Merge "Clean up" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index a80194c..8591a9c 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -77,6 +77,7 @@
"camera_platform_flags_core_java_lib",
"com.android.hardware.input-aconfig-java",
"com.android.input.flags-aconfig-java",
+ "com.android.internal.compat.flags-aconfig-java",
"com.android.internal.foldables.flags-aconfig-java",
"com.android.internal.pm.pkg.component.flags-aconfig-java",
"com.android.media.flags.bettertogether-aconfig-java",
@@ -346,6 +347,12 @@
mode: "test",
}
+cc_aconfig_library {
+ name: "android.os.flags-aconfig-cc-host",
+ aconfig_declarations: "android.os.flags-aconfig",
+ host_supported: true,
+}
+
// VirtualDeviceManager
cc_aconfig_library {
name: "android.companion.virtualdevice.flags-aconfig-cc",
@@ -657,6 +664,13 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// Platform Compat
+java_aconfig_library {
+ name: "com.android.internal.compat.flags-aconfig-java",
+ aconfig_declarations: "compat_logging_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Multi user
aconfig_declarations {
name: "android.multiuser.flags-aconfig",
diff --git a/apex/jobscheduler/service/Android.bp b/apex/jobscheduler/service/Android.bp
index 0104ee1..ace56d4 100644
--- a/apex/jobscheduler/service/Android.bp
+++ b/apex/jobscheduler/service/Android.bp
@@ -20,6 +20,7 @@
],
libs: [
+ "androidx.annotation_annotation",
"app-compat-annotations",
"error_prone_annotations",
"framework",
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.cpp b/cmds/hid/jni/com_android_commands_hid_Device.cpp
index 8b8d361..a142450 100644
--- a/cmds/hid/jni/com_android_commands_hid_Device.cpp
+++ b/cmds/hid/jni/com_android_commands_hid_Device.cpp
@@ -134,8 +134,9 @@
return env;
}
-std::unique_ptr<Device> Device::open(int32_t id, const char* name, int32_t vid, int32_t pid,
- uint16_t bus, const std::vector<uint8_t>& descriptor,
+std::unique_ptr<Device> Device::open(int32_t id, const char* name, const char* uniq, int32_t vid,
+ int32_t pid, uint16_t bus,
+ const std::vector<uint8_t>& descriptor,
std::unique_ptr<DeviceCallback> callback) {
size_t size = descriptor.size();
if (size > HID_MAX_DESCRIPTOR_SIZE) {
@@ -152,8 +153,7 @@
struct uhid_event ev = {};
ev.type = UHID_CREATE2;
strlcpy(reinterpret_cast<char*>(ev.u.create2.name), name, sizeof(ev.u.create2.name));
- std::string uniq = android::base::StringPrintf("Id: %d", id);
- strlcpy(reinterpret_cast<char*>(ev.u.create2.uniq), uniq.c_str(), sizeof(ev.u.create2.uniq));
+ strlcpy(reinterpret_cast<char*>(ev.u.create2.uniq), uniq, sizeof(ev.u.create2.uniq));
memcpy(&ev.u.create2.rd_data, descriptor.data(), size * sizeof(ev.u.create2.rd_data[0]));
ev.u.create2.rd_size = size;
ev.u.create2.bus = bus;
@@ -314,19 +314,31 @@
return data;
}
-static jlong openDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jint id, jint vid,
- jint pid, jint bus, jbyteArray rawDescriptor, jobject callback) {
+static jlong openDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jstring rawUniq, jint id,
+ jint vid, jint pid, jint bus, jbyteArray rawDescriptor, jobject callback) {
ScopedUtfChars name(env, rawName);
if (name.c_str() == nullptr) {
return 0;
}
+ std::string uniq;
+ if (rawUniq != nullptr) {
+ uniq = ScopedUtfChars(env, rawUniq);
+ } else {
+ uniq = android::base::StringPrintf("Id: %d", id);
+ }
+
+ if (uniq.c_str() == nullptr) {
+ return 0;
+ }
+
std::vector<uint8_t> desc = getData(env, rawDescriptor);
std::unique_ptr<uhid::DeviceCallback> cb(new uhid::DeviceCallback(env, callback));
std::unique_ptr<uhid::Device> d =
- uhid::Device::open(id, reinterpret_cast<const char*>(name.c_str()), vid, pid, bus, desc,
+ uhid::Device::open(id, reinterpret_cast<const char*>(name.c_str()),
+ reinterpret_cast<const char*>(uniq.c_str()), vid, pid, bus, desc,
std::move(cb));
return reinterpret_cast<jlong>(d.release());
}
@@ -370,7 +382,7 @@
static JNINativeMethod sMethods[] = {
{"nativeOpenDevice",
- "(Ljava/lang/String;IIII[B"
+ "(Ljava/lang/String;Ljava/lang/String;IIII[B"
"Lcom/android/commands/hid/Device$DeviceCallback;)J",
reinterpret_cast<void*>(openDevice)},
{"nativeSendReport", "(J[B)V", reinterpret_cast<void*>(sendReport)},
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.h b/cmds/hid/jni/com_android_commands_hid_Device.h
index 9c6060d..bc7a909 100644
--- a/cmds/hid/jni/com_android_commands_hid_Device.h
+++ b/cmds/hid/jni/com_android_commands_hid_Device.h
@@ -42,8 +42,9 @@
class Device {
public:
- static std::unique_ptr<Device> open(int32_t id, const char* name, int32_t vid, int32_t pid,
- uint16_t bus, const std::vector<uint8_t>& descriptor,
+ static std::unique_ptr<Device> open(int32_t id, const char* name, const char* uniq, int32_t vid,
+ int32_t pid, uint16_t bus,
+ const std::vector<uint8_t>& descriptor,
std::unique_ptr<DeviceCallback> callback);
~Device();
diff --git a/cmds/hid/src/com/android/commands/hid/Device.java b/cmds/hid/src/com/android/commands/hid/Device.java
index 0415037..4e8adc3 100644
--- a/cmds/hid/src/com/android/commands/hid/Device.java
+++ b/cmds/hid/src/com/android/commands/hid/Device.java
@@ -71,6 +71,7 @@
private static native long nativeOpenDevice(
String name,
+ String uniq,
int id,
int vid,
int pid,
@@ -89,6 +90,7 @@
public Device(
int id,
String name,
+ String uniq,
int vid,
int pid,
int bus,
@@ -113,8 +115,9 @@
} else {
args.arg1 = id + ":" + vid + ":" + pid;
}
- args.arg2 = descriptor;
- args.arg3 = report;
+ args.arg2 = uniq;
+ args.arg3 = descriptor;
+ args.arg4 = report;
mHandler.obtainMessage(MSG_OPEN_DEVICE, args).sendToTarget();
mTimeToSend = SystemClock.uptimeMillis();
}
@@ -167,11 +170,12 @@
mPtr =
nativeOpenDevice(
(String) args.arg1,
+ (String) args.arg2,
args.argi1,
args.argi2,
args.argi3,
args.argi4,
- (byte[]) args.arg2,
+ (byte[]) args.arg3,
new DeviceCallback());
pauseEvents();
break;
diff --git a/cmds/hid/src/com/android/commands/hid/Event.java b/cmds/hid/src/com/android/commands/hid/Event.java
index 3efb797..3b02279 100644
--- a/cmds/hid/src/com/android/commands/hid/Event.java
+++ b/cmds/hid/src/com/android/commands/hid/Event.java
@@ -56,6 +56,7 @@
private int mId;
private String mCommand;
private String mName;
+ private String mUniq;
private byte[] mDescriptor;
private int mVid;
private int mPid;
@@ -78,6 +79,10 @@
return mName;
}
+ public String getUniq() {
+ return mUniq;
+ }
+
public byte[] getDescriptor() {
return mDescriptor;
}
@@ -116,8 +121,9 @@
public String toString() {
return "Event{id=" + mId
- + ", command=" + String.valueOf(mCommand)
- + ", name=" + String.valueOf(mName)
+ + ", command=" + mCommand
+ + ", name=" + mName
+ + ", uniq=" + mUniq
+ ", descriptor=" + Arrays.toString(mDescriptor)
+ ", vid=" + mVid
+ ", pid=" + mPid
@@ -149,6 +155,10 @@
mEvent.mName = name;
}
+ public void setUniq(String uniq) {
+ mEvent.mUniq = uniq;
+ }
+
public void setDescriptor(byte[] descriptor) {
mEvent.mDescriptor = descriptor;
}
@@ -247,6 +257,9 @@
case "name":
eb.setName(mReader.nextString());
break;
+ case "uniq":
+ eb.setUniq(mReader.nextString());
+ break;
case "vid":
eb.setVid(readInt());
break;
diff --git a/cmds/hid/src/com/android/commands/hid/Hid.java b/cmds/hid/src/com/android/commands/hid/Hid.java
index 2db791fe..5ebfd95 100644
--- a/cmds/hid/src/com/android/commands/hid/Hid.java
+++ b/cmds/hid/src/com/android/commands/hid/Hid.java
@@ -117,8 +117,17 @@
"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.getDescriptor(), e.getReport(), e.getFeatureReports(), e.getOutputs());
+ Device d = new Device(
+ id,
+ e.getName(),
+ e.getUniq(),
+ e.getVendorId(),
+ e.getProductId(),
+ e.getBus(),
+ e.getDescriptor(),
+ e.getReport(),
+ e.getFeatureReports(),
+ e.getOutputs());
mDevices.append(id, d);
}
diff --git a/core/api/current.txt b/core/api/current.txt
index b9ad92b..173cf6c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4445,7 +4445,7 @@
method public final android.media.session.MediaController getMediaController();
method @NonNull public android.view.MenuInflater getMenuInflater();
method @NonNull public android.window.OnBackInvokedDispatcher getOnBackInvokedDispatcher();
- method public final android.app.Activity getParent();
+ method @Deprecated public final android.app.Activity getParent();
method @Nullable public android.content.Intent getParentActivityIntent();
method public android.content.SharedPreferences getPreferences(int);
method @Nullable public android.net.Uri getReferrer();
@@ -4463,7 +4463,7 @@
method public void invalidateOptionsMenu();
method public boolean isActivityTransitionRunning();
method public boolean isChangingConfigurations();
- method public final boolean isChild();
+ method @Deprecated public final boolean isChild();
method public boolean isDestroyed();
method public boolean isFinishing();
method public boolean isImmersive();
@@ -4482,7 +4482,7 @@
method @CallSuper public void onActionModeStarted(android.view.ActionMode);
method public void onActivityReenter(int, android.content.Intent);
method protected void onActivityResult(int, int, android.content.Intent);
- method @FlaggedApi("android.security.content_uri_permission_apis") public void onActivityResult(int, int, @NonNull android.content.Intent, @NonNull android.app.ComponentCaller);
+ method @FlaggedApi("android.security.content_uri_permission_apis") public void onActivityResult(int, int, @Nullable android.content.Intent, @NonNull android.app.ComponentCaller);
method @Deprecated public void onAttachFragment(android.app.Fragment);
method public void onAttachedToWindow();
method @Deprecated public void onBackPressed();
@@ -5395,7 +5395,7 @@
public final class AutomaticZenRule implements android.os.Parcelable {
ctor @Deprecated public AutomaticZenRule(String, android.content.ComponentName, android.net.Uri, int, boolean);
- ctor @Deprecated public AutomaticZenRule(@NonNull String, @Nullable android.content.ComponentName, @Nullable android.content.ComponentName, @NonNull android.net.Uri, @Nullable android.service.notification.ZenPolicy, int, boolean);
+ ctor public AutomaticZenRule(@NonNull String, @Nullable android.content.ComponentName, @Nullable android.content.ComponentName, @NonNull android.net.Uri, @Nullable android.service.notification.ZenPolicy, int, boolean);
ctor public AutomaticZenRule(android.os.Parcel);
method public int describeContents();
method public android.net.Uri getConditionId();
@@ -8084,7 +8084,7 @@
method public CharSequence getStartUserSessionMessage(@NonNull android.content.ComponentName);
method @Deprecated public boolean getStorageEncryption(@Nullable android.content.ComponentName);
method public int getStorageEncryptionStatus();
- method @FlaggedApi("android.app.admin.flags.esim_management_enabled") @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS) public java.util.Set<java.lang.Integer> getSubscriptionsIds();
+ method @FlaggedApi("android.app.admin.flags.esim_management_enabled") @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS) public java.util.Set<java.lang.Integer> getSubscriptionIds();
method @Nullable public android.app.admin.SystemUpdatePolicy getSystemUpdatePolicy();
method @Nullable public android.os.PersistableBundle getTransferOwnershipBundle();
method @Nullable public java.util.List<android.os.PersistableBundle> getTrustAgentConfiguration(@Nullable android.content.ComponentName, @NonNull android.content.ComponentName);
@@ -9602,8 +9602,8 @@
method public static android.appwidget.AppWidgetManager getInstance(android.content.Context);
method @FlaggedApi("android.appwidget.flags.generated_previews") @Nullable public android.widget.RemoteViews getWidgetPreview(@NonNull android.content.ComponentName, @Nullable android.os.UserHandle, int);
method public boolean isRequestPinAppWidgetSupported();
- method public void notifyAppWidgetViewDataChanged(int[], int);
- method public void notifyAppWidgetViewDataChanged(int, int);
+ method @Deprecated public void notifyAppWidgetViewDataChanged(int[], int);
+ method @Deprecated public void notifyAppWidgetViewDataChanged(int, int);
method public void partiallyUpdateAppWidget(int[], android.widget.RemoteViews);
method public void partiallyUpdateAppWidget(int, android.widget.RemoteViews);
method @FlaggedApi("android.appwidget.flags.generated_previews") public void removeWidgetPreview(@NonNull android.content.ComponentName, int);
@@ -10731,7 +10731,6 @@
field public static final String DROPBOX_SERVICE = "dropbox";
field public static final String EUICC_SERVICE = "euicc";
field public static final String FILE_INTEGRITY_SERVICE = "file_integrity";
- field public static final String FINGERPRINT_SERVICE = "fingerprint";
field public static final String GAME_SERVICE = "game";
field public static final String GRAMMATICAL_INFLECTION_SERVICE = "grammatical_inflection";
field public static final String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
@@ -15751,6 +15750,7 @@
method public void drawRect(@NonNull android.graphics.RectF, @NonNull android.graphics.Paint);
method public void drawRect(@NonNull android.graphics.Rect, @NonNull android.graphics.Paint);
method public void drawRect(float, float, float, float, @NonNull android.graphics.Paint);
+ method @FlaggedApi("com.android.graphics.hwui.flags.draw_region") public void drawRegion(@NonNull android.graphics.Region, @NonNull android.graphics.Paint);
method public void drawRenderNode(@NonNull android.graphics.RenderNode);
method public void drawRoundRect(@NonNull android.graphics.RectF, float, float, @NonNull android.graphics.Paint);
method public void drawRoundRect(float, float, float, float, float, float, @NonNull android.graphics.Paint);
@@ -18051,24 +18051,6 @@
method public android.graphics.pdf.PdfDocument.PageInfo.Builder setContentRect(android.graphics.Rect);
}
- public final class PdfRenderer implements java.lang.AutoCloseable {
- ctor public PdfRenderer(@NonNull android.os.ParcelFileDescriptor) throws java.io.IOException;
- method public void close();
- method public int getPageCount();
- method public android.graphics.pdf.PdfRenderer.Page openPage(int);
- method public boolean shouldScaleForPrinting();
- }
-
- public final class PdfRenderer.Page implements java.lang.AutoCloseable {
- method public void close();
- method public int getHeight();
- method public int getIndex();
- method public int getWidth();
- method public void render(@NonNull android.graphics.Bitmap, @Nullable android.graphics.Rect, @Nullable android.graphics.Matrix, int);
- field public static final int RENDER_MODE_FOR_DISPLAY = 1; // 0x1
- field public static final int RENDER_MODE_FOR_PRINT = 2; // 0x2
- }
-
}
package android.graphics.text {
@@ -19304,11 +19286,11 @@
method @NonNull public java.util.List<java.lang.Integer> getSupportedExtensions();
method public boolean isCaptureProcessProgressAvailable(int);
method public boolean isPostviewAvailable(int);
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Float>> EFV_PADDING_ZOOM_FACTOR_RANGE;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Float>> EFV_PADDING_ZOOM_FACTOR_RANGE;
field public static final int EXTENSION_AUTOMATIC = 0; // 0x0
field @Deprecated public static final int EXTENSION_BEAUTY = 1; // 0x1
field public static final int EXTENSION_BOKEH = 2; // 0x2
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EXTENSION_EYES_FREE_VIDEOGRAPHY = 5; // 0x5
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EXTENSION_EYES_FREE_VIDEOGRAPHY = 5; // 0x5
field public static final int EXTENSION_FACE_RETOUCH = 1; // 0x1
field public static final int EXTENSION_HDR = 3; // 0x3
field public static final int EXTENSION_NIGHT = 4; // 0x4
@@ -19694,6 +19676,7 @@
public final class CaptureRequest extends android.hardware.camera2.CameraMetadata<android.hardware.camera2.CaptureRequest.Key<?>> implements android.os.Parcelable {
method public int describeContents();
+ method @FlaggedApi("com.android.internal.camera.flags.surface_leak_fix") protected void finalize();
method @Nullable public <T> T get(android.hardware.camera2.CaptureRequest.Key<T>);
method @NonNull public java.util.List<android.hardware.camera2.CaptureRequest.Key<?>> getKeys();
method @Nullable public Object getTag();
@@ -19907,30 +19890,30 @@
field public static final int MAX_THUMBNAIL_DIMENSION = 256; // 0x100
}
- @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class ExtensionCaptureRequest {
+ @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public final class ExtensionCaptureRequest {
ctor public ExtensionCaptureRequest();
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> EFV_AUTO_ZOOM;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_MAX_PADDING_ZOOM_FACTOR;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_PADDING_ZOOM_FACTOR;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_ROTATE_VIEWPORT;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> EFV_STABILIZATION_MODE;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EFV_STABILIZATION_MODE_GIMBAL = 1; // 0x1
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EFV_STABILIZATION_MODE_LOCKED = 2; // 0x2
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EFV_STABILIZATION_MODE_OFF = 0; // 0x0
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.util.Pair<java.lang.Integer,java.lang.Integer>> EFV_TRANSLATE_VIEWPORT;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> EFV_AUTO_ZOOM;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_MAX_PADDING_ZOOM_FACTOR;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_PADDING_ZOOM_FACTOR;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_ROTATE_VIEWPORT;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> EFV_STABILIZATION_MODE;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EFV_STABILIZATION_MODE_GIMBAL = 1; // 0x1
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EFV_STABILIZATION_MODE_LOCKED = 2; // 0x2
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EFV_STABILIZATION_MODE_OFF = 0; // 0x0
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.util.Pair<java.lang.Integer,java.lang.Integer>> EFV_TRANSLATE_VIEWPORT;
}
- @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class ExtensionCaptureResult {
+ @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public final class ExtensionCaptureResult {
ctor public ExtensionCaptureResult();
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> EFV_AUTO_ZOOM;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_MAX_PADDING_ZOOM_FACTOR;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> EFV_PADDING_REGION;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_PADDING_ZOOM_FACTOR;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_ROTATE_VIEWPORT;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EFV_STABILIZATION_MODE;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES;
- field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.util.Pair<java.lang.Integer,java.lang.Integer>> EFV_TRANSLATE_VIEWPORT;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> EFV_AUTO_ZOOM;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_MAX_PADDING_ZOOM_FACTOR;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> EFV_PADDING_REGION;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_PADDING_ZOOM_FACTOR;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_ROTATE_VIEWPORT;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EFV_STABILIZATION_MODE;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES;
+ field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.util.Pair<java.lang.Integer,java.lang.Integer>> EFV_TRANSLATE_VIEWPORT;
}
public class MultiResolutionImageReader implements java.lang.AutoCloseable {
@@ -20379,54 +20362,6 @@
}
-package android.hardware.fingerprint {
-
- @Deprecated public class FingerprintManager {
- method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.USE_BIOMETRIC, android.Manifest.permission.USE_FINGERPRINT}) public void authenticate(@Nullable android.hardware.fingerprint.FingerprintManager.CryptoObject, @Nullable android.os.CancellationSignal, int, @NonNull android.hardware.fingerprint.FingerprintManager.AuthenticationCallback, @Nullable android.os.Handler);
- method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean hasEnrolledFingerprints();
- method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean isHardwareDetected();
- field public static final int FINGERPRINT_ACQUIRED_GOOD = 0; // 0x0
- field public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; // 0x3
- field public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; // 0x2
- field public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; // 0x1
- field public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; // 0x5
- field public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; // 0x4
- field public static final int FINGERPRINT_ERROR_CANCELED = 5; // 0x5
- field public static final int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12; // 0xc
- field public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; // 0x1
- field public static final int FINGERPRINT_ERROR_LOCKOUT = 7; // 0x7
- field public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; // 0x9
- field public static final int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11; // 0xb
- field public static final int FINGERPRINT_ERROR_NO_SPACE = 4; // 0x4
- field public static final int FINGERPRINT_ERROR_TIMEOUT = 3; // 0x3
- field public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; // 0x2
- field public static final int FINGERPRINT_ERROR_USER_CANCELED = 10; // 0xa
- field public static final int FINGERPRINT_ERROR_VENDOR = 8; // 0x8
- }
-
- @Deprecated public abstract static class FingerprintManager.AuthenticationCallback {
- ctor @Deprecated public FingerprintManager.AuthenticationCallback();
- method @Deprecated public void onAuthenticationError(int, CharSequence);
- method @Deprecated public void onAuthenticationFailed();
- method @Deprecated public void onAuthenticationHelp(int, CharSequence);
- method @Deprecated public void onAuthenticationSucceeded(android.hardware.fingerprint.FingerprintManager.AuthenticationResult);
- }
-
- @Deprecated public static class FingerprintManager.AuthenticationResult {
- method @Deprecated public android.hardware.fingerprint.FingerprintManager.CryptoObject getCryptoObject();
- }
-
- @Deprecated public static final class FingerprintManager.CryptoObject {
- ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull java.security.Signature);
- ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Cipher);
- ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Mac);
- method @Deprecated public javax.crypto.Cipher getCipher();
- method @Deprecated public javax.crypto.Mac getMac();
- method @Deprecated public java.security.Signature getSignature();
- }
-
-}
-
package android.hardware.input {
public final class HostUsiVersion implements android.os.Parcelable {
@@ -28119,7 +28054,7 @@
method public void sendSigningResult(@NonNull String, @NonNull byte[]);
method public void sendTrackInfoList(@Nullable java.util.List<android.media.tv.TvTrackInfo>);
method public void setCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdView.TvAdCallback);
- method public void setOnUnhandledInputEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdView.OnUnhandledInputEventListener);
+ method public void setOnUnhandledInputEventListener(@NonNull android.media.tv.ad.TvAdView.OnUnhandledInputEventListener);
method public boolean setTvView(@Nullable android.media.tv.TvView);
method public void startAdService();
method public void stopAdService();
@@ -52512,8 +52447,8 @@
method public final void cancelPendingInputEvents();
method public boolean checkInputConnectionProxy(android.view.View);
method public void clearAnimation();
- method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void clearCredentialManagerRequest();
method public void clearFocus();
+ method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void clearPendingCredentialRequest();
method public void clearViewTranslationCallback();
method public static int combineMeasuredStates(int, int);
method protected int computeHorizontalScrollExtent();
@@ -52622,8 +52557,6 @@
method @FlaggedApi("android.view.flags.sensitive_content_app_protection_api") public final int getContentSensitivity();
method @UiContext public final android.content.Context getContext();
method protected android.view.ContextMenu.ContextMenuInfo getContextMenuInfo();
- method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public final android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException> getCredentialManagerCallback();
- method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public final android.credentials.GetCredentialRequest getCredentialManagerRequest();
method public final boolean getDefaultFocusHighlightEnabled();
method public static int getDefaultSize(int, int);
method public android.view.Display getDisplay();
@@ -52708,6 +52641,8 @@
method public int getPaddingTop();
method public final android.view.ViewParent getParent();
method public android.view.ViewParent getParentForAccessibility();
+ method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public final android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException> getPendingCredentialCallback();
+ method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public final android.credentials.GetCredentialRequest getPendingCredentialRequest();
method public float getPivotX();
method public float getPivotY();
method public android.view.PointerIcon getPointerIcon();
@@ -53008,7 +52943,6 @@
method public void setContentDescription(CharSequence);
method @FlaggedApi("android.view.flags.sensitive_content_app_protection_api") public final void setContentSensitivity(int);
method public void setContextClickable(boolean);
- method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void setCredentialManagerRequest(@NonNull android.credentials.GetCredentialRequest, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
method public void setDefaultFocusHighlightEnabled(boolean);
method @Deprecated public void setDrawingCacheBackgroundColor(@ColorInt int);
method @Deprecated public void setDrawingCacheEnabled(boolean);
@@ -53087,6 +53021,7 @@
method public void setOverScrollMode(int);
method public void setPadding(int, int, int, int);
method public void setPaddingRelative(int, int, int, int);
+ method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void setPendingCredentialRequest(@NonNull android.credentials.GetCredentialRequest, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
method public void setPivotX(float);
method public void setPivotY(float);
method public void setPointerIcon(android.view.PointerIcon);
@@ -53890,10 +53825,10 @@
method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void clearCredentialManagerRequest();
method @Nullable public abstract android.view.autofill.AutofillId getAutofillId();
method public abstract int getChildCount();
- method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException> getCredentialManagerCallback();
- method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public android.credentials.GetCredentialRequest getCredentialManagerRequest();
method public abstract android.os.Bundle getExtras();
method public abstract CharSequence getHint();
+ method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException> getPendingCredentialCallback();
+ method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public android.credentials.GetCredentialRequest getPendingCredentialRequest();
method public abstract CharSequence getText();
method public abstract int getTextSelectionEnd();
method public abstract int getTextSelectionStart();
@@ -53916,7 +53851,6 @@
method public abstract void setClickable(boolean);
method public abstract void setContentDescription(CharSequence);
method public abstract void setContextClickable(boolean);
- method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void setCredentialManagerRequest(@NonNull android.credentials.GetCredentialRequest, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
method public abstract void setDataIsSensitive(boolean);
method public abstract void setDimens(int, int, int, int, int, int);
method public abstract void setElevation(float);
@@ -53935,6 +53869,7 @@
method public void setMaxTextLength(int);
method public void setMinTextEms(int);
method public abstract void setOpaque(boolean);
+ method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void setPendingCredentialRequest(@NonNull android.credentials.GetCredentialRequest, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
method public void setReceiveContentMimeTypes(@Nullable String[]);
method public abstract void setSelected(boolean);
method public abstract void setText(CharSequence);
@@ -57980,7 +57915,7 @@
method public abstract boolean getBuiltInZoomControls();
method public abstract int getCacheMode();
method public abstract String getCursiveFontFamily();
- method public abstract boolean getDatabaseEnabled();
+ method @Deprecated public abstract boolean getDatabaseEnabled();
method @Deprecated public abstract String getDatabasePath();
method public abstract int getDefaultFixedFontSize();
method public abstract int getDefaultFontSize();
@@ -58026,7 +57961,7 @@
method public abstract void setBuiltInZoomControls(boolean);
method public abstract void setCacheMode(int);
method public abstract void setCursiveFontFamily(String);
- method public abstract void setDatabaseEnabled(boolean);
+ method @Deprecated public abstract void setDatabaseEnabled(boolean);
method @Deprecated public abstract void setDatabasePath(String);
method public abstract void setDefaultFixedFontSize(int);
method public abstract void setDefaultFontSize(int);
@@ -60222,7 +60157,7 @@
method public void setRadioGroupChecked(@IdRes int, @IdRes int);
method public void setRelativeScrollPosition(@IdRes int, int);
method @Deprecated public void setRemoteAdapter(int, @IdRes int, android.content.Intent);
- method public void setRemoteAdapter(@IdRes int, android.content.Intent);
+ method @Deprecated public void setRemoteAdapter(@IdRes int, android.content.Intent);
method public void setRemoteAdapter(@IdRes int, @NonNull android.widget.RemoteViews.RemoteCollectionItems);
method public void setScrollPosition(@IdRes int, int);
method public void setShort(@IdRes int, String, short);
diff --git a/core/api/removed.txt b/core/api/removed.txt
index 3c7c0d6..c61f163 100644
--- a/core/api/removed.txt
+++ b/core/api/removed.txt
@@ -35,6 +35,7 @@
method @Deprecated @Nullable public String getFeatureId();
method public abstract android.content.SharedPreferences getSharedPreferences(java.io.File, int);
method public abstract java.io.File getSharedPreferencesPath(String);
+ field public static final String FINGERPRINT_SERVICE = "fingerprint";
}
public class ContextWrapper extends android.content.Context {
@@ -145,6 +146,54 @@
}
+package android.hardware.fingerprint {
+
+ @Deprecated public class FingerprintManager {
+ method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.USE_BIOMETRIC, android.Manifest.permission.USE_FINGERPRINT}) public void authenticate(@Nullable android.hardware.fingerprint.FingerprintManager.CryptoObject, @Nullable android.os.CancellationSignal, int, @NonNull android.hardware.fingerprint.FingerprintManager.AuthenticationCallback, @Nullable android.os.Handler);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean hasEnrolledFingerprints();
+ method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean isHardwareDetected();
+ field public static final int FINGERPRINT_ACQUIRED_GOOD = 0; // 0x0
+ field public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; // 0x3
+ field public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; // 0x2
+ field public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; // 0x1
+ field public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; // 0x5
+ field public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; // 0x4
+ field public static final int FINGERPRINT_ERROR_CANCELED = 5; // 0x5
+ field public static final int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12; // 0xc
+ field public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; // 0x1
+ field public static final int FINGERPRINT_ERROR_LOCKOUT = 7; // 0x7
+ field public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; // 0x9
+ field public static final int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11; // 0xb
+ field public static final int FINGERPRINT_ERROR_NO_SPACE = 4; // 0x4
+ field public static final int FINGERPRINT_ERROR_TIMEOUT = 3; // 0x3
+ field public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; // 0x2
+ field public static final int FINGERPRINT_ERROR_USER_CANCELED = 10; // 0xa
+ field public static final int FINGERPRINT_ERROR_VENDOR = 8; // 0x8
+ }
+
+ @Deprecated public abstract static class FingerprintManager.AuthenticationCallback {
+ ctor public FingerprintManager.AuthenticationCallback();
+ method public void onAuthenticationError(int, CharSequence);
+ method public void onAuthenticationFailed();
+ method public void onAuthenticationHelp(int, CharSequence);
+ method public void onAuthenticationSucceeded(android.hardware.fingerprint.FingerprintManager.AuthenticationResult);
+ }
+
+ @Deprecated public static class FingerprintManager.AuthenticationResult {
+ method public android.hardware.fingerprint.FingerprintManager.CryptoObject getCryptoObject();
+ }
+
+ @Deprecated public static final class FingerprintManager.CryptoObject {
+ ctor public FingerprintManager.CryptoObject(@NonNull java.security.Signature);
+ ctor public FingerprintManager.CryptoObject(@NonNull javax.crypto.Cipher);
+ ctor public FingerprintManager.CryptoObject(@NonNull javax.crypto.Mac);
+ method public javax.crypto.Cipher getCipher();
+ method public javax.crypto.Mac getMac();
+ method public java.security.Signature getSignature();
+ }
+
+}
+
package android.media {
public final class AudioFormat implements android.os.Parcelable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index b73f199..a551f0d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1306,6 +1306,7 @@
public class DevicePolicyManager {
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int checkProvisioningPrecondition(@NonNull String, @NonNull String);
+ method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void clearAuditLogEventCallback();
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
method @Nullable public android.content.Intent createProvisioningIntentFromNfcIntent(@NonNull android.content.Intent);
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void finalizeWorkProfileProvisioning(@NonNull android.os.UserHandle, @Nullable android.accounts.Account);
@@ -1342,7 +1343,7 @@
method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException;
method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public void setApplicationExemptions(@NonNull String, @NonNull java.util.Set<java.lang.Integer>) throws android.content.pm.PackageManager.NameNotFoundException;
method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEnabled(boolean);
- method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEventCallback(@NonNull java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.util.List<android.app.admin.SecurityLog.SecurityEvent>>);
+ method @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEventCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.admin.SecurityLog.SecurityEvent>>);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied();
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean);
method @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setMaxPolicyStorageLimit(int);
@@ -2189,14 +2190,6 @@
package android.app.ondeviceintelligence {
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class Content implements android.os.Parcelable {
- ctor public Content(@NonNull android.os.Bundle);
- method public int describeContents();
- method @NonNull public android.os.Bundle getData();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.Content> CREATOR;
- }
-
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface DownloadCallback {
method public void onDownloadCompleted(@NonNull android.os.PersistableBundle);
method public void onDownloadFailed(int, @Nullable String, @NonNull android.os.PersistableBundle);
@@ -2232,11 +2225,11 @@
}
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class FeatureDetails implements android.os.Parcelable {
- ctor public FeatureDetails(@android.app.ondeviceintelligence.FeatureDetails.Status int, @NonNull android.os.PersistableBundle);
- ctor public FeatureDetails(@android.app.ondeviceintelligence.FeatureDetails.Status int);
+ ctor public FeatureDetails(int, @NonNull android.os.PersistableBundle);
+ ctor public FeatureDetails(int);
method public int describeContents();
method @NonNull public android.os.PersistableBundle getFeatureDetailParams();
- method @android.app.ondeviceintelligence.FeatureDetails.Status public int getFeatureStatus();
+ method public int getFeatureStatus();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.FeatureDetails> CREATOR;
field public static final int FEATURE_STATUS_AVAILABLE = 3; // 0x3
@@ -2246,35 +2239,14 @@
field public static final int FEATURE_STATUS_UNAVAILABLE = 0; // 0x0
}
- @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE_USE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD}) public static @interface FeatureDetails.Status {
- }
-
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public class OnDeviceIntelligenceManager {
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeature(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- method @Nullable @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public String getRemoteServicePackageName();
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getVersion(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.LongConsumer);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void listFeatures(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.ProcessingOutcomeReceiver);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestFeatureDownload(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.DownloadCallback);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenInfo(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- field public static final int REQUEST_TYPE_EMBEDDINGS = 2; // 0x2
- field public static final int REQUEST_TYPE_INFERENCE = 0; // 0x0
- field public static final int REQUEST_TYPE_PREPARE = 1; // 0x1
- }
-
- public static class OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException extends java.lang.Exception {
- ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException(int, @NonNull String, @NonNull android.os.PersistableBundle);
- ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException(int, @NonNull android.os.PersistableBundle);
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public class OnDeviceIntelligenceException extends java.lang.Exception {
+ ctor public OnDeviceIntelligenceException(int, @NonNull String, @NonNull android.os.PersistableBundle);
+ ctor public OnDeviceIntelligenceException(int, @NonNull android.os.PersistableBundle);
+ ctor public OnDeviceIntelligenceException(int, @NonNull String);
+ ctor public OnDeviceIntelligenceException(int);
method public int getErrorCode();
method @NonNull public android.os.PersistableBundle getErrorParams();
- field public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 1000; // 0x3e8
- }
-
- public static class OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException extends android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException {
- ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException(int, @NonNull String, @NonNull android.os.PersistableBundle);
- ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException(int, @NonNull android.os.PersistableBundle);
+ field public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 100; // 0x64
field public static final int PROCESSING_ERROR_BAD_DATA = 2; // 0x2
field public static final int PROCESSING_ERROR_BAD_REQUEST = 3; // 0x3
field public static final int PROCESSING_ERROR_BUSY = 9; // 0x9
@@ -2290,10 +2262,28 @@
field public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15; // 0xf
field public static final int PROCESSING_ERROR_SUSPENDED = 13; // 0xd
field public static final int PROCESSING_ERROR_UNKNOWN = 1; // 0x1
+ field public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 200; // 0xc8
}
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface ProcessingOutcomeReceiver extends android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> {
- method public default void onDataAugmentRequest(@NonNull android.app.ondeviceintelligence.Content, @NonNull java.util.function.Consumer<android.app.ondeviceintelligence.Content>);
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class OnDeviceIntelligenceManager {
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeature(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ method @Nullable @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public String getRemoteServicePackageName();
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getVersion(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.LongConsumer);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void listFeatures(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.ProcessingCallback);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamingProcessingCallback);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestFeatureDownload(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.DownloadCallback);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenInfo(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ field public static final int REQUEST_TYPE_EMBEDDINGS = 2; // 0x2
+ field public static final int REQUEST_TYPE_INFERENCE = 0; // 0x0
+ field public static final int REQUEST_TYPE_PREPARE = 1; // 0x1
+ }
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface ProcessingCallback {
+ method public default void onDataAugmentRequest(@NonNull android.os.Bundle, @NonNull java.util.function.Consumer<android.os.Bundle>);
+ method public void onError(@NonNull android.app.ondeviceintelligence.OnDeviceIntelligenceException);
+ method public void onResult(@NonNull android.os.Bundle);
}
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class ProcessingSignal {
@@ -2306,8 +2296,8 @@
method public void onSignalReceived(@NonNull android.os.PersistableBundle);
}
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamedProcessingOutcomeReceiver extends android.app.ondeviceintelligence.ProcessingOutcomeReceiver {
- method public void onNewContent(@NonNull android.app.ondeviceintelligence.Content);
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamingProcessingCallback extends android.app.ondeviceintelligence.ProcessingCallback {
+ method public void onPartialResult(@NonNull android.os.Bundle);
}
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class TokenInfo implements android.os.Parcelable {
@@ -3347,7 +3337,7 @@
field public static final int STATUS_SERVICE_UNAVAILABLE = 3; // 0x3
field public static final int STATUS_SUCCESS = 1; // 0x1
field public static final int STATUS_UNKNOWN = 0; // 0x0
- field public static final int STATUS_UNSUPPORTED = 2; // 0x2
+ field @Deprecated public static final int STATUS_UNSUPPORTED = 2; // 0x2
field @FlaggedApi("android.app.wearable.enable_data_request_observer_api") public static final int STATUS_UNSUPPORTED_DATA_TYPE = 8; // 0x8
field @FlaggedApi("android.app.wearable.enable_unsupported_operation_status_code") public static final int STATUS_UNSUPPORTED_OPERATION = 6; // 0x6
field public static final int STATUS_WEARABLE_UNAVAILABLE = 4; // 0x4
@@ -4911,7 +4901,6 @@
method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int getImageFormat();
method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.util.Size getSize();
method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.view.Surface getSurface();
- method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void setColorSpace(int);
method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void setDynamicRangeProfile(long);
}
@@ -4922,6 +4911,7 @@
@FlaggedApi("com.android.internal.camera.flags.concert_mode") public class ExtensionConfiguration {
ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public ExtensionConfiguration(int, int, @NonNull java.util.List<android.hardware.camera2.extension.ExtensionOutputConfiguration>, @Nullable android.hardware.camera2.CaptureRequest);
+ method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void setColorSpace(int);
}
@FlaggedApi("com.android.internal.camera.flags.concert_mode") public class ExtensionOutputConfiguration {
@@ -6260,7 +6250,7 @@
method public void addOnCompleteListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.OnCompleteListener);
method public void addOnCompleteListener(@NonNull android.hardware.radio.ProgramList.OnCompleteListener);
method public void close();
- method @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier);
+ method @Deprecated @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier);
method @FlaggedApi("android.hardware.radio.hd_radio_improved") @NonNull public java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramInfos(@NonNull android.hardware.radio.ProgramSelector.Identifier);
method public void registerListCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.ListCallback);
method public void registerListCallback(@NonNull android.hardware.radio.ProgramList.ListCallback);
@@ -6312,7 +6302,7 @@
field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5; // 0x5
field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SID_EXT = 5; // 0x5
field public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10; // 0xa
- field public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb
+ field @Deprecated public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb
field public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; // 0x9
field public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3; // 0x3
field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int IDENTIFIER_TYPE_HD_STATION_LOCATION = 15; // 0xf
@@ -6320,8 +6310,8 @@
field @Deprecated public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; // 0x4
field public static final int IDENTIFIER_TYPE_INVALID = 0; // 0x0
field public static final int IDENTIFIER_TYPE_RDS_PI = 2; // 0x2
- field public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd
- field public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc
+ field @Deprecated public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd
+ field @Deprecated public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc
field public static final int IDENTIFIER_TYPE_VENDOR_END = 1999; // 0x7cf
field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = 1999; // 0x7cf
field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = 1000; // 0x3e8
@@ -6374,7 +6364,7 @@
field public static final int CONFIG_DAB_DAB_SOFT_LINKING = 8; // 0x8
field public static final int CONFIG_DAB_FM_LINKING = 7; // 0x7
field public static final int CONFIG_DAB_FM_SOFT_LINKING = 9; // 0x9
- field public static final int CONFIG_FORCE_ANALOG = 2; // 0x2
+ field @Deprecated public static final int CONFIG_FORCE_ANALOG = 2; // 0x2
field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_AM = 11; // 0xb
field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_FM = 10; // 0xa
field public static final int CONFIG_FORCE_DIGITAL = 3; // 0x3
@@ -12955,38 +12945,26 @@
ctor public OnDeviceIntelligenceService();
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
method public abstract void onDownloadFeature(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull android.app.ondeviceintelligence.DownloadCallback);
- method public abstract void onGetFeature(int, int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- method public abstract void onGetFeatureDetails(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ method public abstract void onGetFeature(int, int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ method public abstract void onGetFeatureDetails(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
method public abstract void onGetReadOnlyFeatureFileDescriptorMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,android.os.ParcelFileDescriptor>>);
method public abstract void onGetVersion(@NonNull java.util.function.LongConsumer);
method public abstract void onInferenceServiceConnected();
method public abstract void onInferenceServiceDisconnected();
- method public abstract void onListFeatures(int, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- method public final void updateProcessingState(@NonNull android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException>);
+ method public abstract void onListFeatures(int, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ method public final void updateProcessingState(@NonNull android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceIntelligenceService";
}
- public abstract static class OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException extends java.lang.Exception {
- ctor public OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException(int);
- ctor public OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException(int, @NonNull String);
- method public int getErrorCode();
- }
-
- public static class OnDeviceIntelligenceService.OnDeviceUpdateProcessingException extends android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException {
- ctor public OnDeviceIntelligenceService.OnDeviceUpdateProcessingException(int);
- ctor public OnDeviceIntelligenceService.OnDeviceUpdateProcessingException(int, @NonNull String);
- field public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 1; // 0x1
- }
-
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceSandboxedInferenceService extends android.app.Service {
ctor public OnDeviceSandboxedInferenceService();
method public final void fetchFeatureFileInputStreamMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.io.FileInputStream>>);
method @NonNull public java.util.concurrent.Executor getCallbackExecutor();
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
- method @NonNull public abstract void onProcessRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.ProcessingOutcomeReceiver);
- method @NonNull public abstract void onProcessRequestStreaming(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver);
- method @NonNull public abstract void onTokenInfoRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
- method public abstract void onUpdateProcessingState(@NonNull android.os.Bundle, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException>);
+ method @NonNull public abstract void onProcessRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.ProcessingCallback);
+ method @NonNull public abstract void onProcessRequestStreaming(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamingProcessingCallback);
+ method @NonNull public abstract void onTokenInfoRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
+ method public abstract void onUpdateProcessingState(@NonNull android.os.Bundle, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.app.ondeviceintelligence.OnDeviceIntelligenceException>);
method public final java.io.FileInputStream openFileInput(@NonNull String) throws java.io.FileNotFoundException;
method public final void openFileInputAsync(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.io.FileInputStream>) throws java.io.FileNotFoundException;
field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService";
@@ -14213,7 +14191,6 @@
method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsSupportingScheme(String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean isInEmergencyCall();
method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isInSelfManagedCall(@NonNull String, @NonNull android.os.UserHandle);
- method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isInSelfManagedCall(@NonNull String, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRinging();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUserSelectedOutgoingPhoneAccount(@Nullable android.telecom.PhoneAccountHandle);
field public static final String ACTION_CURRENT_TTY_MODE_CHANGED = "android.telecom.action.CURRENT_TTY_MODE_CHANGED";
@@ -15393,7 +15370,7 @@
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean matchesCurrentSimOperator(@NonNull String, int, @Nullable String);
method public boolean needsOtaServiceProvisioning();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled();
- method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(android.Manifest.permission.DUMP) public void persistEmergencyCallDiagnosticData(@NonNull String, @NonNull android.telephony.TelephonyManager.EmergencyCallDiagnosticData);
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(android.Manifest.permission.READ_DROPBOX_DATA) public void persistEmergencyCallDiagnosticData(@NonNull String, @NonNull android.telephony.TelephonyManager.EmergencyCallDiagnosticData);
method @RequiresPermission(android.Manifest.permission.REBOOT) public int prepareForUnattendedReboot();
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerCarrierPrivilegesCallback(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CarrierPrivilegesCallback);
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 1e72a06..62fc67b 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -1885,8 +1885,6 @@
Documentation mentions 'TODO'
Todo: android.app.NotificationManager#isNotificationAssistantAccessGranted(android.content.ComponentName):
Documentation mentions 'TODO'
-Todo: android.app.ondeviceintelligence.OnDeviceIntelligenceManager#requestFeatureDownload(android.app.ondeviceintelligence.Feature, android.app.ondeviceintelligence.CancellationSignal, java.util.concurrent.Executor, android.app.ondeviceintelligence.DownloadCallback):
- Documentation mentions 'TODO'
Todo: android.hardware.camera2.params.StreamConfigurationMap:
Documentation mentions 'TODO'
Todo: android.hardware.location.ContextHubManager#getNanoAppInstanceInfo(int):
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 53d0c03..bc45a76 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -42,6 +42,7 @@
field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE";
field public static final String READ_WRITE_SYNC_DISABLED_MODE_CONFIG = "android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG";
field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
+ field @FlaggedApi("android.permission.flags.sensitive_notification_app_protection") public static final String RECORD_SENSITIVE_CONTENT = "android.permission.RECORD_SENSITIVE_CONTENT";
field public static final String REMAP_MODIFIER_KEYS = "android.permission.REMAP_MODIFIER_KEYS";
field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
field public static final String REQUEST_UNIQUE_ID_ATTESTATION = "android.permission.REQUEST_UNIQUE_ID_ATTESTATION";
@@ -1624,8 +1625,6 @@
package android.hardware.devicestate {
@FlaggedApi("android.hardware.devicestate.feature.flags.device_state_property_api") public final class DeviceState {
- ctor @Deprecated public DeviceState(@IntRange(from=android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE_IDENTIFIER, to=android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE_IDENTIFIER) int, @NonNull String, int);
- ctor public DeviceState(@IntRange(from=android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE_IDENTIFIER, to=android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE_IDENTIFIER) int, @NonNull String, @NonNull java.util.Set<java.lang.Integer>);
field public static final int PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST = 8; // 0x8
}
@@ -1710,15 +1709,6 @@
}
-package android.hardware.fingerprint {
-
- @Deprecated public class FingerprintManager {
- method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public android.hardware.biometrics.BiometricTestSession createTestSession(int);
- method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public java.util.List<android.hardware.biometrics.SensorProperties> getSensorProperties();
- }
-
-}
-
package android.hardware.hdmi {
public final class HdmiControlServiceWrapper {
@@ -3930,6 +3920,7 @@
}
public final class InputMethodInfo implements android.os.Parcelable {
+ ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, boolean, @NonNull String);
ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, @NonNull String, boolean, @NonNull String);
ctor @FlaggedApi("android.view.inputmethod.connectionless_handwriting") public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, @NonNull String, boolean, boolean, @NonNull String);
ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, int);
diff --git a/core/api/test-removed.txt b/core/api/test-removed.txt
index d802177..2e44176 100644
--- a/core/api/test-removed.txt
+++ b/core/api/test-removed.txt
@@ -1 +1,10 @@
// Signature format: 2.0
+package android.hardware.fingerprint {
+
+ @Deprecated public class FingerprintManager {
+ method @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public android.hardware.biometrics.BiometricTestSession createTestSession(int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public java.util.List<android.hardware.biometrics.SensorProperties> getSensorProperties();
+ }
+
+}
+
diff --git a/core/java/Android.bp b/core/java/Android.bp
index ab1c9a4..4f96206 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -167,6 +167,9 @@
"com/android/internal/logging/UiEventLoggerImpl.java",
":statslog-framework-java-gen",
],
+ libs: [
+ "androidx.annotation_annotation",
+ ],
static_libs: ["modules-utils-uieventlogger-interface"],
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 63cafdc..1cc2d25 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1126,8 +1126,8 @@
* @hide
*/
@Override
- public void updateStatusBarAppearance(int appearance) {
- mTaskDescription.setStatusBarAppearance(appearance);
+ public void updateSystemBarsAppearance(int appearance) {
+ mTaskDescription.setSystemBarsAppearance(appearance);
setTaskDescription(mTaskDescription);
}
@@ -1254,12 +1254,23 @@
return mApplication;
}
- /** Is this activity embedded inside of another activity? */
+ /**
+ * Whether this is a child {@link Activity} of an {@link ActivityGroup}.
+ *
+ * @deprecated {@link ActivityGroup} is deprecated.
+ */
+ @Deprecated
public final boolean isChild() {
return mParent != null;
}
- /** Return the parent activity if this view is an embedded child. */
+ /**
+ * Returns the parent {@link Activity} if this is a child {@link Activity} of an
+ * {@link ActivityGroup}.
+ *
+ * @deprecated {@link ActivityGroup} is deprecated.
+ */
+ @Deprecated
public final Activity getParent() {
return mParent;
}
@@ -5535,6 +5546,15 @@
}
a.recycle();
+ if (first && mTaskDescription.getSystemBarsAppearance() == 0
+ && mWindow != null && mWindow.getSystemBarAppearance() != 0) {
+ // When the theme is applied for the first time during the activity re-creation process,
+ // the attached window restores the system bars appearance from the old window/activity.
+ // Make sure to restore this appearance in TaskDescription too, to prevent the
+ // #setTaskDescription() call below from incorrectly sending an empty value to the
+ // server.
+ mTaskDescription.setSystemBarsAppearance(mWindow.getSystemBarAppearance());
+ }
setTaskDescription(mTaskDescription);
}
@@ -7453,7 +7473,7 @@
* intent.
*/
@FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS)
- public void onActivityResult(int requestCode, int resultCode, @NonNull Intent data,
+ public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data,
@NonNull ComponentCaller caller) {
onActivityResult(requestCode, resultCode, data);
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index f358522..fae4348 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1604,7 +1604,7 @@
private int mStatusBarColor;
private int mNavigationBarColor;
@Appearance
- private int mStatusBarAppearance;
+ private int mSystemBarsAppearance;
private boolean mEnsureStatusBarContrastWhenTransparent;
private boolean mEnsureNavigationBarContrastWhenTransparent;
private int mResizeMode;
@@ -1804,7 +1804,7 @@
public TaskDescription(@Nullable String label, @Nullable Icon icon,
int colorPrimary, int colorBackground,
int statusBarColor, int navigationBarColor,
- @Appearance int statusBarAppearance,
+ @Appearance int systemBarsAppearance,
boolean ensureStatusBarContrastWhenTransparent,
boolean ensureNavigationBarContrastWhenTransparent, int resizeMode, int minWidth,
int minHeight, int colorBackgroundFloating) {
@@ -1814,7 +1814,7 @@
mColorBackground = colorBackground;
mStatusBarColor = statusBarColor;
mNavigationBarColor = navigationBarColor;
- mStatusBarAppearance = statusBarAppearance;
+ mSystemBarsAppearance = systemBarsAppearance;
mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent;
mEnsureNavigationBarContrastWhenTransparent =
ensureNavigationBarContrastWhenTransparent;
@@ -1843,7 +1843,7 @@
mColorBackground = other.mColorBackground;
mStatusBarColor = other.mStatusBarColor;
mNavigationBarColor = other.mNavigationBarColor;
- mStatusBarAppearance = other.mStatusBarAppearance;
+ mSystemBarsAppearance = other.mSystemBarsAppearance;
mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
mEnsureNavigationBarContrastWhenTransparent =
other.mEnsureNavigationBarContrastWhenTransparent;
@@ -1873,8 +1873,8 @@
if (other.mNavigationBarColor != 0) {
mNavigationBarColor = other.mNavigationBarColor;
}
- if (other.mStatusBarAppearance != 0) {
- mStatusBarAppearance = other.mStatusBarAppearance;
+ if (other.mSystemBarsAppearance != 0) {
+ mSystemBarsAppearance = other.mSystemBarsAppearance;
}
mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
@@ -2148,8 +2148,8 @@
* @hide
*/
@Appearance
- public int getStatusBarAppearance() {
- return mStatusBarAppearance;
+ public int getSystemBarsAppearance() {
+ return mSystemBarsAppearance;
}
/**
@@ -2163,8 +2163,8 @@
/**
* @hide
*/
- public void setStatusBarAppearance(@Appearance int statusBarAppearance) {
- mStatusBarAppearance = statusBarAppearance;
+ public void setSystemBarsAppearance(@Appearance int systemBarsAppearance) {
+ mSystemBarsAppearance = systemBarsAppearance;
}
/**
@@ -2291,7 +2291,7 @@
dest.writeInt(mColorBackground);
dest.writeInt(mStatusBarColor);
dest.writeInt(mNavigationBarColor);
- dest.writeInt(mStatusBarAppearance);
+ dest.writeInt(mSystemBarsAppearance);
dest.writeBoolean(mEnsureStatusBarContrastWhenTransparent);
dest.writeBoolean(mEnsureNavigationBarContrastWhenTransparent);
dest.writeInt(mResizeMode);
@@ -2315,7 +2315,7 @@
mColorBackground = source.readInt();
mStatusBarColor = source.readInt();
mNavigationBarColor = source.readInt();
- mStatusBarAppearance = source.readInt();
+ mSystemBarsAppearance = source.readInt();
mEnsureStatusBarContrastWhenTransparent = source.readBoolean();
mEnsureNavigationBarContrastWhenTransparent = source.readBoolean();
mResizeMode = source.readInt();
@@ -2347,7 +2347,8 @@
? " (contrast when transparent)" : "")
+ " resizeMode: " + ActivityInfo.resizeModeToString(mResizeMode)
+ " minWidth: " + mMinWidth + " minHeight: " + mMinHeight
- + " colorBackgrounFloating: " + mColorBackgroundFloating;
+ + " colorBackgrounFloating: " + mColorBackgroundFloating
+ + " systemBarsAppearance: " + mSystemBarsAppearance;
}
@Override
@@ -2367,7 +2368,7 @@
result = result * 31 + mColorBackgroundFloating;
result = result * 31 + mStatusBarColor;
result = result * 31 + mNavigationBarColor;
- result = result * 31 + mStatusBarAppearance;
+ result = result * 31 + mSystemBarsAppearance;
result = result * 31 + (mEnsureStatusBarContrastWhenTransparent ? 1 : 0);
result = result * 31 + (mEnsureNavigationBarContrastWhenTransparent ? 1 : 0);
result = result * 31 + mResizeMode;
@@ -2390,7 +2391,7 @@
&& mColorBackground == other.mColorBackground
&& mStatusBarColor == other.mStatusBarColor
&& mNavigationBarColor == other.mNavigationBarColor
- && mStatusBarAppearance == other.mStatusBarAppearance
+ && mSystemBarsAppearance == other.mSystemBarsAppearance
&& mEnsureStatusBarContrastWhenTransparent
== other.mEnsureStatusBarContrastWhenTransparent
&& mEnsureNavigationBarContrastWhenTransparent
@@ -6053,20 +6054,6 @@
}
/**
- * Checks if the "modern" broadcast queue is enabled.
- *
- * @hide
- */
- @RequiresPermission(android.Manifest.permission.DUMP)
- public boolean isModernBroadcastQueueEnabled() {
- try {
- return getService().isModernBroadcastQueueEnabled();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* Checks if the process represented by the given {@code pid} is frozen.
*
* @hide
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 062b89e..e28a6ce 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -226,12 +226,6 @@
public abstract boolean isSystemReady();
/**
- * @return {@code true} if system is using the "modern" broadcast queue,
- * {@code false} otherwise.
- */
- public abstract boolean isModernQueueEnabled();
-
- /**
* Enforce capability restrictions on use of the given BroadcastOptions
*/
public abstract void enforceBroadcastOptionsPermissions(@Nullable Bundle options,
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 1d39186..ae5cacd 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -974,6 +974,7 @@
ContentCaptureOptions contentCaptureOptions;
long[] disabledCompatChanges;
+ long[] mLoggableCompatChanges;
SharedMemory mSerializedSystemFontMap;
@@ -1283,6 +1284,7 @@
AutofillOptions autofillOptions,
ContentCaptureOptions contentCaptureOptions,
long[] disabledCompatChanges,
+ long[] loggableCompatChanges,
SharedMemory serializedSystemFontMap,
long startRequestedElapsedTime,
long startRequestedUptime) {
@@ -1337,6 +1339,7 @@
data.autofillOptions = autofillOptions;
data.contentCaptureOptions = contentCaptureOptions;
data.disabledCompatChanges = disabledCompatChanges;
+ data.mLoggableCompatChanges = loggableCompatChanges;
data.mSerializedSystemFontMap = serializedSystemFontMap;
data.startRequestedElapsedTime = startRequestedElapsedTime;
data.startRequestedUptime = startRequestedUptime;
@@ -4073,6 +4076,13 @@
ActivityManager.getService().waitForNetworkStateUpdate(mNetworkBlockSeq);
mNetworkBlockSeq = INVALID_PROC_STATE_SEQ;
} catch (RemoteException ignored) {}
+ if (Flags.clearDnsCacheOnNetworkRulesUpdate()) {
+ // InetAddress will cache UnknownHostException failures. If the rules got
+ // updated and the app has network access now, we need to clear the negative
+ // cache to ensure valid dns queries can work immediately.
+ // TODO: b/329133769 - Clear only the negative cache once it is available.
+ InetAddress.clearDnsCache();
+ }
}
}
}
@@ -7123,7 +7133,7 @@
Process.setStartTimes(SystemClock.elapsedRealtime(), SystemClock.uptimeMillis(),
data.startRequestedElapsedTime, data.startRequestedUptime);
- AppCompatCallbacks.install(data.disabledCompatChanges);
+ AppCompatCallbacks.install(data.disabledCompatChanges, data.mLoggableCompatChanges);
// Let libcore handle any compat changes after installing the list of compat changes.
AppSpecializationHooks.handleCompatChangesBeforeBindingApplication();
diff --git a/core/java/android/app/AppCompatCallbacks.java b/core/java/android/app/AppCompatCallbacks.java
index 134cef5..f2debfc 100644
--- a/core/java/android/app/AppCompatCallbacks.java
+++ b/core/java/android/app/AppCompatCallbacks.java
@@ -30,41 +30,59 @@
*/
public final class AppCompatCallbacks implements Compatibility.BehaviorChangeDelegate {
private final long[] mDisabledChanges;
+ private final long[] mLoggableChanges;
private final ChangeReporter mChangeReporter;
/**
- * Install this class into the current process.
+ * Install this class into the current process using the disabled and loggable changes lists.
*
* @param disabledChanges Set of compatibility changes that are disabled for this process.
+ * @param loggableChanges Set of compatibility changes that we want to log.
*/
- public static void install(long[] disabledChanges) {
- Compatibility.setBehaviorChangeDelegate(new AppCompatCallbacks(disabledChanges));
+ public static void install(long[] disabledChanges, long[] loggableChanges) {
+ Compatibility.setBehaviorChangeDelegate(
+ new AppCompatCallbacks(disabledChanges, loggableChanges));
}
- private AppCompatCallbacks(long[] disabledChanges) {
+ private AppCompatCallbacks(long[] disabledChanges, long[] loggableChanges) {
mDisabledChanges = Arrays.copyOf(disabledChanges, disabledChanges.length);
+ mLoggableChanges = Arrays.copyOf(loggableChanges, loggableChanges.length);
Arrays.sort(mDisabledChanges);
- mChangeReporter = new ChangeReporter(
- ChangeReporter.SOURCE_APP_PROCESS);
+ Arrays.sort(mLoggableChanges);
+ mChangeReporter = new ChangeReporter(ChangeReporter.SOURCE_APP_PROCESS);
+ }
+
+ /**
+ * Helper to determine if a list contains a changeId.
+ *
+ * @param list to search through
+ * @param changeId for which to search in the list
+ * @return true if the given changeId is found in the provided array.
+ */
+ private boolean changeIdInChangeList(long[] list, long changeId) {
+ return Arrays.binarySearch(list, changeId) >= 0;
}
public void onChangeReported(long changeId) {
- reportChange(changeId, ChangeReporter.STATE_LOGGED);
+ boolean isLoggable = changeIdInChangeList(mLoggableChanges, changeId);
+ reportChange(changeId, ChangeReporter.STATE_LOGGED, isLoggable);
}
public boolean isChangeEnabled(long changeId) {
- if (Arrays.binarySearch(mDisabledChanges, changeId) < 0) {
- // Not present in the disabled array
- reportChange(changeId, ChangeReporter.STATE_ENABLED);
+ boolean isEnabled = !changeIdInChangeList(mDisabledChanges, changeId);
+ boolean isLoggable = changeIdInChangeList(mLoggableChanges, changeId);
+ if (isEnabled) {
+ // Not present in the disabled changeId array
+ reportChange(changeId, ChangeReporter.STATE_ENABLED, isLoggable);
return true;
}
- reportChange(changeId, ChangeReporter.STATE_DISABLED);
+ reportChange(changeId, ChangeReporter.STATE_DISABLED, isLoggable);
return false;
}
- private void reportChange(long changeId, int state) {
+ private void reportChange(long changeId, int state, boolean isLoggable) {
int uid = Process.myUid();
- mChangeReporter.reportChange(uid, changeId, state);
+ mChangeReporter.reportChange(uid, changeId, state, isLoggable);
}
}
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index 5e2397d..33d41d3 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -185,6 +185,8 @@
/**
* Creates an automatic zen rule.
*
+ * <p>Note: Prefer {@link AutomaticZenRule.Builder} to construct an {@link AutomaticZenRule}.
+ *
* @param name The name of the rule.
* @param owner The Condition Provider service that owns this rule. This can be null if you're
* using {@link NotificationManager#setAutomaticZenRuleState(String, Condition)}
@@ -206,10 +208,7 @@
* while this rule is active. This overrides the global policy while this rule is
* action ({@link Condition#STATE_TRUE}).
* @param enabled Whether the rule is enabled.
- *
- * @deprecated Use {@link AutomaticZenRule.Builder} to construct an {@link AutomaticZenRule}.
*/
- @Deprecated
public AutomaticZenRule(@NonNull String name, @Nullable ComponentName owner,
@Nullable ComponentName configurationActivity, @NonNull Uri conditionId,
@Nullable ZenPolicy policy, int interruptionFilter, boolean enabled) {
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index af56cb4..ed0c933 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -272,10 +272,17 @@
@UnsupportedAppUsage
private Context mOuterContext;
+
+ private final Object mThemeLock = new Object();
+
@UnsupportedAppUsage
+ @GuardedBy("mThemeLock")
private int mThemeResource = 0;
+
@UnsupportedAppUsage
+ @GuardedBy("mThemeLock")
private Resources.Theme mTheme = null;
+
@UnsupportedAppUsage
private PackageManager mPackageManager;
private Context mReceiverRestrictedContext = null;
@@ -288,7 +295,6 @@
private ContentCaptureOptions mContentCaptureOptions = null;
- private final Object mSync = new Object();
/**
* Indicates this {@link Context} can not handle UI components properly and is not associated
* with a {@link Display} instance.
@@ -340,21 +346,18 @@
*/
private boolean mOwnsToken = false;
- @GuardedBy("mSync")
- private File mDatabasesDir;
- @GuardedBy("mSync")
- @UnsupportedAppUsage
- private File mPreferencesDir;
- @GuardedBy("mSync")
- private File mFilesDir;
- @GuardedBy("mSync")
- private File mCratesDir;
- @GuardedBy("mSync")
- private File mNoBackupFilesDir;
- @GuardedBy("mSync")
- private File mCacheDir;
- @GuardedBy("mSync")
- private File mCodeCacheDir;
+ private final Object mDirsLock = new Object();
+ private volatile File mDatabasesDir;
+ @UnsupportedAppUsage private volatile File mPreferencesDir;
+ private volatile File mFilesDir;
+ private volatile File mCratesDir;
+ private volatile File mNoBackupFilesDir;
+ private volatile File[] mExternalFilesDirs;
+ private volatile File[] mObbDirs;
+ private volatile File mCacheDir;
+ private volatile File mCodeCacheDir;
+ private volatile File[] mExternalCacheDirs;
+ private volatile File[] mExternalMediaDirs;
// The system service cache for the system services that are cached per-ContextImpl.
@UnsupportedAppUsage
@@ -458,7 +461,7 @@
@Override
public void setTheme(int resId) {
- synchronized (mSync) {
+ synchronized (mThemeLock) {
if (mThemeResource != resId) {
mThemeResource = resId;
initializeTheme();
@@ -468,14 +471,14 @@
@Override
public int getThemeResId() {
- synchronized (mSync) {
+ synchronized (mThemeLock) {
return mThemeResource;
}
}
@Override
public Resources.Theme getTheme() {
- synchronized (mSync) {
+ synchronized (mThemeLock) {
if (mTheme != null) {
return mTheme;
}
@@ -488,6 +491,7 @@
}
}
+ @GuardedBy("mThemeLock")
private void initializeTheme() {
if (mTheme == null) {
mTheme = mResources.newTheme();
@@ -597,12 +601,18 @@
if (sp == null) {
checkMode(mode);
if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
- if (isCredentialProtectedStorage()
- && !getSystemService(UserManager.class)
- .isUserUnlockingOrUnlocked(UserHandle.myUserId())) {
- throw new IllegalStateException("SharedPreferences in credential encrypted "
- + "storage are not available until after user (id "
- + UserHandle.myUserId() + ") is unlocked");
+ if (isCredentialProtectedStorage()) {
+ final UserManager um = getSystemService(UserManager.class);
+ if (um == null) {
+ throw new IllegalStateException("SharedPreferences cannot be accessed "
+ + "if UserManager is not available. "
+ + "(e.g. from inside an isolated process)");
+ }
+ if (!um.isUserUnlockingOrUnlocked(UserHandle.myUserId())) {
+ throw new IllegalStateException("SharedPreferences in "
+ + "credential encrypted storage are not available until after "
+ + "user (id " + UserHandle.myUserId() + ") is unlocked");
+ }
}
}
sp = new SharedPreferencesImpl(file, mode);
@@ -731,12 +741,18 @@
@UnsupportedAppUsage
private File getPreferencesDir() {
- synchronized (mSync) {
- if (mPreferencesDir == null) {
- mPreferencesDir = new File(getDataDir(), "shared_prefs");
+ File localPreferencesDir = mPreferencesDir;
+ if (localPreferencesDir == null) {
+ synchronized (mDirsLock) {
+ localPreferencesDir = mPreferencesDir;
+ if (localPreferencesDir == null) {
+ localPreferencesDir = new File(getDataDir(), "shared_prefs");
+ ensurePrivateDirExists(localPreferencesDir);
+ mPreferencesDir = localPreferencesDir;
+ }
}
- return ensurePrivateDirExists(mPreferencesDir);
}
+ return localPreferencesDir;
}
@Override
@@ -778,16 +794,16 @@
/**
* Common-path handling of app data dir creation
*/
- private static File ensurePrivateDirExists(File file) {
- return ensurePrivateDirExists(file, 0771, -1, null);
+ private static void ensurePrivateDirExists(File file) {
+ ensurePrivateDirExists(file, 0771, -1, null);
}
- private static File ensurePrivateCacheDirExists(File file, String xattr) {
+ private static void ensurePrivateCacheDirExists(File file, String xattr) {
final int gid = UserHandle.getCacheAppGid(Process.myUid());
- return ensurePrivateDirExists(file, 02771, gid, xattr);
+ ensurePrivateDirExists(file, 02771, gid, xattr);
}
- private static File ensurePrivateDirExists(File file, int mode, int gid, String xattr) {
+ private static void ensurePrivateDirExists(File file, int mode, int gid, String xattr) {
if (!file.exists()) {
final String path = file.getAbsolutePath();
try {
@@ -815,17 +831,22 @@
}
}
}
- return file;
}
@Override
public File getFilesDir() {
- synchronized (mSync) {
- if (mFilesDir == null) {
- mFilesDir = new File(getDataDir(), "files");
+ File localFilesDir = mFilesDir;
+ if (localFilesDir == null) {
+ localFilesDir = mFilesDir;
+ synchronized (mDirsLock) {
+ if (localFilesDir == null) {
+ localFilesDir = new File(getDataDir(), "files");
+ ensurePrivateDirExists(localFilesDir);
+ mFilesDir = localFilesDir;
+ }
}
- return ensurePrivateDirExists(mFilesDir);
}
+ return localFilesDir;
}
@Override
@@ -835,25 +856,37 @@
final Path absoluteNormalizedCratePath = cratesRootPath.resolve(crateId)
.toAbsolutePath().normalize();
- synchronized (mSync) {
- if (mCratesDir == null) {
- mCratesDir = cratesRootPath.toFile();
+ File localCratesDir = mCratesDir;
+ if (localCratesDir == null) {
+ synchronized (mDirsLock) {
+ localCratesDir = mCratesDir;
+ if (localCratesDir == null) {
+ localCratesDir = cratesRootPath.toFile();
+ ensurePrivateDirExists(localCratesDir);
+ mCratesDir = localCratesDir;
+ }
}
- ensurePrivateDirExists(mCratesDir);
}
- File cratedDir = absoluteNormalizedCratePath.toFile();
- return ensurePrivateDirExists(cratedDir);
+ File crateDir = absoluteNormalizedCratePath.toFile();
+ ensurePrivateDirExists(crateDir);
+ return crateDir;
}
@Override
public File getNoBackupFilesDir() {
- synchronized (mSync) {
- if (mNoBackupFilesDir == null) {
- mNoBackupFilesDir = new File(getDataDir(), "no_backup");
+ File localNoBackupFilesDir = mNoBackupFilesDir;
+ if (localNoBackupFilesDir == null) {
+ synchronized (mDirsLock) {
+ localNoBackupFilesDir = mNoBackupFilesDir;
+ if (localNoBackupFilesDir == null) {
+ localNoBackupFilesDir = new File(getDataDir(), "no_backup");
+ ensurePrivateDirExists(localNoBackupFilesDir);
+ mNoBackupFilesDir = localNoBackupFilesDir;
+ }
}
- return ensurePrivateDirExists(mNoBackupFilesDir);
}
+ return localNoBackupFilesDir;
}
@Override
@@ -865,13 +898,24 @@
@Override
public File[] getExternalFilesDirs(String type) {
- synchronized (mSync) {
- File[] dirs = Environment.buildExternalStorageAppFilesDirs(getPackageName());
- if (type != null) {
- dirs = Environment.buildPaths(dirs, type);
+ File[] localExternalFilesDirs = mExternalFilesDirs;
+ if (localExternalFilesDirs == null) {
+ synchronized (mDirsLock) {
+ localExternalFilesDirs = mExternalFilesDirs;
+ if (localExternalFilesDirs == null) {
+ localExternalFilesDirs =
+ Environment.buildExternalStorageAppFilesDirs(getPackageName());
+ if (type != null) {
+ localExternalFilesDirs =
+ Environment.buildPaths(localExternalFilesDirs, type);
+ }
+ localExternalFilesDirs = ensureExternalDirsExistOrFilter(
+ localExternalFilesDirs, true /* tryCreateInProcess */);
+ mExternalFilesDirs = localExternalFilesDirs;
+ }
}
- return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
}
+ return localExternalFilesDirs;
}
@Override
@@ -883,30 +927,51 @@
@Override
public File[] getObbDirs() {
- synchronized (mSync) {
- File[] dirs = Environment.buildExternalStorageAppObbDirs(getPackageName());
- return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
+ File[] localObbDirs = mObbDirs;
+ if (mObbDirs == null) {
+ synchronized (mDirsLock) {
+ localObbDirs = mObbDirs;
+ if (localObbDirs == null) {
+ localObbDirs = Environment.buildExternalStorageAppObbDirs(getPackageName());
+ localObbDirs = ensureExternalDirsExistOrFilter(
+ localObbDirs, true /* tryCreateInProcess */);
+ mObbDirs = localObbDirs;
+ }
+ }
}
+ return localObbDirs;
}
@Override
public File getCacheDir() {
- synchronized (mSync) {
- if (mCacheDir == null) {
- mCacheDir = new File(getDataDir(), "cache");
+ File localCacheDir = mCacheDir;
+ if (localCacheDir == null) {
+ synchronized (mDirsLock) {
+ localCacheDir = mCacheDir;
+ if (localCacheDir == null) {
+ localCacheDir = new File(getDataDir(), "cache");
+ ensurePrivateCacheDirExists(localCacheDir, XATTR_INODE_CACHE);
+ mCacheDir = localCacheDir;
+ }
}
- return ensurePrivateCacheDirExists(mCacheDir, XATTR_INODE_CACHE);
}
+ return localCacheDir;
}
@Override
public File getCodeCacheDir() {
- synchronized (mSync) {
- if (mCodeCacheDir == null) {
- mCodeCacheDir = getCodeCacheDirBeforeBind(getDataDir());
+ File localCodeCacheDir = mCodeCacheDir;
+ if (localCodeCacheDir == null) {
+ synchronized (mDirsLock) {
+ localCodeCacheDir = mCodeCacheDir;
+ if (localCodeCacheDir == null) {
+ localCodeCacheDir = getCodeCacheDirBeforeBind(getDataDir());
+ ensurePrivateCacheDirExists(localCodeCacheDir, XATTR_INODE_CODE_CACHE);
+ mCodeCacheDir = localCodeCacheDir;
+ }
}
- return ensurePrivateCacheDirExists(mCodeCacheDir, XATTR_INODE_CODE_CACHE);
}
+ return localCodeCacheDir;
}
/**
@@ -927,21 +992,37 @@
@Override
public File[] getExternalCacheDirs() {
- synchronized (mSync) {
- File[] dirs = Environment.buildExternalStorageAppCacheDirs(getPackageName());
- // We don't try to create cache directories in-process, because they need special
- // setup for accurate quota tracking. This ensures the cache dirs are always
- // created through StorageManagerService.
- return ensureExternalDirsExistOrFilter(dirs, false /* tryCreateInProcess */);
+ File[] localExternalCacheDirs = mExternalCacheDirs;
+ if (localExternalCacheDirs == null) {
+ synchronized (mDirsLock) {
+ localExternalCacheDirs = mExternalCacheDirs;
+ if (localExternalCacheDirs == null) {
+ localExternalCacheDirs =
+ Environment.buildExternalStorageAppCacheDirs(getPackageName());
+ localExternalCacheDirs = ensureExternalDirsExistOrFilter(
+ localExternalCacheDirs, false /* tryCreateInProcess */);
+ mExternalCacheDirs = localExternalCacheDirs;
+ }
+ }
}
+ return localExternalCacheDirs;
}
@Override
public File[] getExternalMediaDirs() {
- synchronized (mSync) {
- File[] dirs = Environment.buildExternalStorageAppMediaDirs(getPackageName());
- return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
+ File[] localExternalMediaDirs = mExternalMediaDirs;
+ if (localExternalMediaDirs == null) {
+ synchronized (mDirsLock) {
+ localExternalMediaDirs = mExternalMediaDirs;
+ if (localExternalMediaDirs == null) {
+ localExternalMediaDirs = Environment.buildExternalStorageAppMediaDirs(getPackageName());
+ localExternalMediaDirs = ensureExternalDirsExistOrFilter(
+ localExternalMediaDirs, true /* tryCreateInProcess */);
+ mExternalMediaDirs = localExternalMediaDirs;
+ }
+ }
}
+ return localExternalMediaDirs;
}
/**
@@ -1040,16 +1121,22 @@
}
private File getDatabasesDir() {
- synchronized (mSync) {
- if (mDatabasesDir == null) {
- if ("android".equals(getPackageName())) {
- mDatabasesDir = new File("/data/system");
- } else {
- mDatabasesDir = new File(getDataDir(), "databases");
+ File localDatabasesDir = mDatabasesDir;
+ if (localDatabasesDir == null) {
+ synchronized (mDirsLock) {
+ localDatabasesDir = mDatabasesDir;
+ if (localDatabasesDir == null) {
+ if ("android".equals(getPackageName())) {
+ localDatabasesDir = new File("/data/system");
+ } else {
+ localDatabasesDir = new File(getDataDir(), "databases");
+ }
+ ensurePrivateDirExists(localDatabasesDir);
+ mDatabasesDir = localDatabasesDir;
}
}
- return ensurePrivateDirExists(mDatabasesDir);
}
+ return localDatabasesDir;
}
@Override
diff --git a/core/java/android/app/DreamManager.java b/core/java/android/app/DreamManager.java
index 7c8b0fd..ef6982e 100644
--- a/core/java/android/app/DreamManager.java
+++ b/core/java/android/app/DreamManager.java
@@ -185,6 +185,22 @@
}
/**
+ * Whether dreaming can start given user settings and the current dock/charge state.
+ *
+ * @hide
+ */
+ @UserHandleAware
+ @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE)
+ public boolean canStartDreaming(boolean isScreenOn) {
+ try {
+ return mService.canStartDreaming(isScreenOn);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return false;
+ }
+
+ /**
* Returns whether the device is Dreaming.
*
* <p> This is only used for testing the dream service APIs.
diff --git a/core/java/android/app/GrammaticalInflectionManager.java b/core/java/android/app/GrammaticalInflectionManager.java
index 4ce983f..3e7d665 100644
--- a/core/java/android/app/GrammaticalInflectionManager.java
+++ b/core/java/android/app/GrammaticalInflectionManager.java
@@ -125,7 +125,10 @@
/**
* Get the current grammatical gender of privileged application from the encrypted file.
*
- * @return the value of grammatical gender
+ * @return the value of system grammatical gender only if the calling app has the permission,
+ * otherwise throwing an exception.
+ *
+ * @throws SecurityException if the caller does not have the required permission.
*
* @see Configuration#getGrammaticalGender
*/
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 7a95720..5e6b54b 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -898,10 +898,6 @@
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.DUMP)")
void forceDelayBroadcastDelivery(in String targetPackage, long delayedDurationMs);
- /** Checks if the modern broadcast queue is enabled. */
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.DUMP)")
- boolean isModernBroadcastQueueEnabled();
-
/** Checks if the process represented by the given pid is frozen. */
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.DUMP)")
boolean isProcessFrozen(int pid);
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index a04620c..251e4e8 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -90,7 +90,7 @@
in CompatibilityInfo compatInfo, in Map services,
in Bundle coreSettings, in String buildSerial, in AutofillOptions autofillOptions,
in ContentCaptureOptions contentCaptureOptions, in long[] disabledCompatChanges,
- in SharedMemory serializedSystemFontMap,
+ in long[] loggableCompatChanges, in SharedMemory serializedSystemFontMap,
long startRequestedElapsedTime, long startRequestedUptime);
void runIsolatedEntryPoint(in String entryPoint, in String[] entryPointArgs);
void scheduleExit();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 79cb09d..cd4c0bc 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -7531,6 +7531,9 @@
/**
* Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is
* attached to.
+ * <p>
+ * Note: Calling build() multiple times returns the same Notification instance,
+ * so reusing a builder to create multiple Notifications is discouraged.
*
* @return the fully constructed Notification.
*/
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index ab5395e..9f2e473 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -562,6 +562,12 @@
* audio attributes. Notification channels with an {@link #getImportance() importance} of at
* least {@link NotificationManager#IMPORTANCE_DEFAULT} should have a sound.
*
+ * Note: An app-specific sound can be provided in the Uri parameter, but because channels are
+ * persistent for the duration of the app install, and are backed up and restored, the Uri
+ * should be stable. For this reason it is not recommended to use a
+ * {@link ContentResolver#SCHEME_ANDROID_RESOURCE} uri, as resource ids can change on app
+ * upgrade.
+ *
* Only modifiable before the channel is submitted to
* {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
*/
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index d01626e..fa4a400 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -34,6 +34,8 @@
import android.app.contentsuggestions.IContentSuggestionsManager;
import android.app.ecm.EnhancedConfirmationFrameworkInitializer;
import android.app.job.JobSchedulerFrameworkInitializer;
+import android.app.ondeviceintelligence.IOnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
import android.app.people.PeopleManager;
import android.app.prediction.AppPredictionManager;
import android.app.role.RoleFrameworkInitializer;
@@ -1589,6 +1591,19 @@
return new WearableSensingManager(ctx.getOuterContext(), manager);
}});
+ registerService(Context.ON_DEVICE_INTELLIGENCE_SERVICE, OnDeviceIntelligenceManager.class,
+ new CachedServiceFetcher<OnDeviceIntelligenceManager>() {
+ @Override
+ public OnDeviceIntelligenceManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder iBinder = ServiceManager.getServiceOrThrow(
+ Context.ON_DEVICE_INTELLIGENCE_SERVICE);
+ IOnDeviceIntelligenceManager manager =
+ IOnDeviceIntelligenceManager.Stub.asInterface(iBinder);
+ return new OnDeviceIntelligenceManager(ctx.getOuterContext(), manager);
+ }
+ });
+
registerService(Context.GRAMMATICAL_INFLECTION_SERVICE, GrammaticalInflectionManager.class,
new CachedServiceFetcher<GrammaticalInflectionManager>() {
@Override
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 986205a..9ef8b38 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -189,10 +189,13 @@
@FlaggedApi(FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
public static final int HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER = 2;
+ /**
+ * @hide
+ */
@IntDef({HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED, HEADLESS_DEVICE_OWNER_MODE_AFFILIATED,
HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER})
@Retention(RetentionPolicy.SOURCE)
- private @interface HeadlessDeviceOwnerMode {}
+ public @interface HeadlessDeviceOwnerMode {}
/** @hide */
public static class PolicyInfo {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index b25ebf6..cb4ed058 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -53,6 +53,7 @@
import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY;
import static android.Manifest.permission.SET_TIME;
import static android.Manifest.permission.SET_TIME_ZONE;
+import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
import static android.app.admin.flags.Flags.FLAG_DEVICE_THEFT_API_ENABLED;
import static android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED;
import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED;
@@ -93,6 +94,7 @@
import android.app.IServiceConnection;
import android.app.KeyguardManager;
import android.app.admin.SecurityLog.SecurityEvent;
+import android.app.admin.flags.Flags;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
@@ -14090,9 +14092,7 @@
try {
return mService.isAuditLogEnabled(mContext.getPackageName());
} catch (RemoteException re) {
- re.rethrowFromSystemServer();
- // unreachable
- return false;
+ throw re.rethrowFromSystemServer();
}
}
@@ -14102,8 +14102,8 @@
* is enforced by the caller. Disabling the policy clears the callback. Each time a new callback
* is set, it will first be invoked with all the audit log events available at the time.
*
- * @param callback callback to invoke when new audit log events become available or {@code null}
- * to clear the callback.
+ * @param callback The callback to invoke when new audit log events become available.
+ * @param executor The executor through which the callback should be invoked.
* @hide
*/
@SystemApi
@@ -14111,11 +14111,10 @@
@RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
public void setAuditLogEventCallback(
@NonNull @CallbackExecutor Executor executor,
- @Nullable Consumer<List<SecurityEvent>> callback) {
+ @NonNull Consumer<List<SecurityEvent>> callback) {
throwIfParentInstance("setAuditLogEventCallback");
- final IAuditLogEventsCallback wrappedCallback = callback == null
- ? null
- : new IAuditLogEventsCallback.Stub() {
+ final IAuditLogEventsCallback wrappedCallback =
+ new IAuditLogEventsCallback.Stub() {
@Override
public void onNewAuditLogEvents(List<SecurityEvent> events) {
executor.execute(() -> callback.accept(events));
@@ -14124,7 +14123,25 @@
try {
mService.setAuditLogEventsCallback(mContext.getPackageName(), wrappedCallback);
} catch (RemoteException re) {
- re.rethrowFromSystemServer();
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Clears audit log event callback. If a callback was set previously, it may still get invoked
+ * after this call returns if it was already scheduled.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED)
+ @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
+ public void clearAuditLogEventCallback() {
+ throwIfParentInstance("clearAuditLogEventCallback");
+ try {
+ mService.setAuditLogEventsCallback(mContext.getPackageName(), null);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
}
}
@@ -17442,24 +17459,24 @@
}
/**
- * Returns the subscription ids of all subscriptions which was downloaded by the calling
+ * Returns the subscription ids of all subscriptions which were downloaded by the calling
* admin.
*
* <p> This returns only the subscriptions which were downloaded by the calling admin via
* {@link android.telephony.euicc.EuiccManager#downloadSubscription}.
- * If a susbcription is returned by this method then in it subject to management controls
+ * If a subscription is returned by this method then in it subject to management controls
* and cannot be removed by users.
*
* <p> Callable by device owners and profile owners.
*
- * @throws SecurityException if the caller is not authorized to call this method
- * @return ids of all managed subscriptions currently downloaded by an admin on the device
+ * @throws SecurityException if the caller is not authorized to call this method.
+ * @return ids of all managed subscriptions currently downloaded by an admin on the device.
*/
@FlaggedApi(FLAG_ESIM_MANAGEMENT_ENABLED)
@RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS)
@NonNull
- public Set<Integer> getSubscriptionsIds() {
- throwIfParentInstance("getSubscriptionsIds");
+ public Set<Integer> getSubscriptionIds() {
+ throwIfParentInstance("getSubscriptionIds");
if (mService != null) {
try {
return intArrayToSet(mService.getSubscriptionIds(mContext.getPackageName()));
@@ -17511,4 +17528,25 @@
}
return -1;
}
+
+ /**
+ * @return The headless device owner mode for the current set DO, returns
+ * {@link DeviceAdminInfo#HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED} if no DO is set.
+ *
+ * @hide
+ */
+ @DeviceAdminInfo.HeadlessDeviceOwnerMode
+ public int getHeadlessDeviceOwnerMode() {
+ if (!Flags.headlessDeviceOwnerProvisioningFixEnabled()) {
+ return HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
+ }
+ if (mService != null) {
+ try {
+ return mService.getHeadlessDeviceOwnerMode(mContext.getPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
+ }
}
\ No newline at end of file
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 3a7a891c..03d0b0f 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -625,4 +625,6 @@
void setMaxPolicyStorageLimit(String packageName, int storageLimit);
int getMaxPolicyStorageLimit(String packageName);
+
+ int getHeadlessDeviceOwnerMode(String callerPackageName);
}
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 1927019..c29ea6d 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -163,3 +163,13 @@
description: "Enable UX changes for esim management"
bug: "295301164"
}
+
+flag {
+ name: "headless_device_owner_provisioning_fix_enabled"
+ namespace: "enterprise"
+ description: "Fix provisioning for single-user headless DO"
+ bug: "289515470"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 89199ca..7548562 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -1297,7 +1297,7 @@
*/
@FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
@Nullable
- public GetCredentialRequest getCredentialManagerRequest() {
+ public GetCredentialRequest getPendingCredentialRequest() {
return mGetCredentialRequest;
}
@@ -1306,7 +1306,7 @@
*/
@FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
@Nullable
- public ResultReceiver getCredentialManagerCallback() {
+ public ResultReceiver getPendingCredentialCallback() {
return mGetCredentialResultReceiver;
}
@@ -2191,14 +2191,14 @@
@Nullable
@Override
- public GetCredentialRequest getCredentialManagerRequest() {
+ public GetCredentialRequest getPendingCredentialRequest() {
return mNode.mGetCredentialRequest;
}
@Nullable
@Override
public OutcomeReceiver<
- GetCredentialResponse, GetCredentialException> getCredentialManagerCallback() {
+ GetCredentialResponse, GetCredentialException> getPendingCredentialCallback() {
return mNode.mGetCredentialCallback;
}
@@ -2267,7 +2267,7 @@
}
@Override
- public void setCredentialManagerRequest(@NonNull GetCredentialRequest request,
+ public void setPendingCredentialRequest(@NonNull GetCredentialRequest request,
@NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
mNode.mGetCredentialRequest = request;
mNode.mGetCredentialCallback = callback;
@@ -2654,7 +2654,7 @@
+ ", isCredential=" + node.isCredential()
);
}
- GetCredentialRequest getCredentialRequest = node.getCredentialManagerRequest();
+ GetCredentialRequest getCredentialRequest = node.getPendingCredentialRequest();
if (getCredentialRequest == null) {
Log.i(TAG, prefix + " No Credential Manager Request");
} else {
diff --git a/core/java/android/app/network-policy.aconfig b/core/java/android/app/network-policy.aconfig
new file mode 100644
index 0000000..88f386f
--- /dev/null
+++ b/core/java/android/app/network-policy.aconfig
@@ -0,0 +1,11 @@
+package: "android.app"
+
+flag {
+ namespace: "backstage_power"
+ name: "clear_dns_cache_on_network_rules_update"
+ description: "Clears the DNS cache when the network rules update"
+ bug: "237556596"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
\ No newline at end of file
diff --git a/core/java/android/app/ondeviceintelligence/Content.java b/core/java/android/app/ondeviceintelligence/Content.java
deleted file mode 100644
index 51bd156..0000000
--- a/core/java/android/app/ondeviceintelligence/Content.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.ondeviceintelligence;
-
-import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
-
-import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
-import android.annotation.SystemApi;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.Objects;
-
-/**
- * Represents content sent to and received from the on-device inference service.
- * Can contain a collection of text, image, and binary parts or any combination of these.
- *
- * @hide
- */
-@SystemApi
-@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public final class Content implements Parcelable {
- //TODO: Improve javadoc after adding validation logic.
- private static final String TAG = "Content";
- private final Bundle mData;
-
- /**
- * Create a content object using a Bundle of only known types that are read-only.
- */
- public Content(@NonNull Bundle data) {
- Objects.requireNonNull(data);
- validateBundleData(data);
- this.mData = data;
- }
-
- /**
- * Returns the Content's data represented as a Bundle.
- */
- @NonNull
- public Bundle getData() {
- return mData;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeBundle(mData);
- }
-
- @Override
- public int describeContents() {
- int mask = 0;
- mask |= mData.describeContents();
- return mask;
- }
-
- @NonNull
- public static final Creator<Content> CREATOR = new Creator<>() {
- @Override
- @NonNull
- public Content createFromParcel(@NonNull Parcel in) {
- return new Content(in.readBundle(getClass().getClassLoader()));
- }
-
- @Override
- @NonNull
- public Content[] newArray(int size) {
- return new Content[size];
- }
- };
-
- private void validateBundleData(Bundle unused) {
- // TODO: Validate there are only known types.
- }
-}
diff --git a/core/java/android/app/ondeviceintelligence/DownloadCallback.java b/core/java/android/app/ondeviceintelligence/DownloadCallback.java
index 684c71f..30c6e19 100644
--- a/core/java/android/app/ondeviceintelligence/DownloadCallback.java
+++ b/core/java/android/app/ondeviceintelligence/DownloadCallback.java
@@ -105,7 +105,7 @@
}
/**
- * Called when model download via MDD completed. The remote implementation can populate any
+ * Called when model download is completed. The remote implementation can populate any
* associated download params like file stats etc. in this callback to inform the client.
*
* @param downloadParams params containing info about the completed download.
diff --git a/core/java/android/app/ondeviceintelligence/Feature.java b/core/java/android/app/ondeviceintelligence/Feature.java
index 4a38c92..fd0379a 100644
--- a/core/java/android/app/ondeviceintelligence/Feature.java
+++ b/core/java/android/app/ondeviceintelligence/Feature.java
@@ -34,7 +34,6 @@
@SystemApi
@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
public final class Feature implements Parcelable {
- // TODO(b/325315604) - Check if we can expose non-hidden IntDefs in Framework.
private final int mId;
@Nullable
private final String mName;
diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.java b/core/java/android/app/ondeviceintelligence/FeatureDetails.java
index f3cbd26..44930f2 100644
--- a/core/java/android/app/ondeviceintelligence/FeatureDetails.java
+++ b/core/java/android/app/ondeviceintelligence/FeatureDetails.java
@@ -60,6 +60,9 @@
/** Underlying service is unavailable and feature status cannot be fetched. */
public static final int FEATURE_STATUS_SERVICE_UNAVAILABLE = 4;
+ /**
+ * @hide
+ */
@IntDef(value = {
FEATURE_STATUS_UNAVAILABLE,
FEATURE_STATUS_DOWNLOADABLE,
diff --git a/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl b/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
index aba563f..8fc269e 100644
--- a/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
+++ b/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
@@ -16,15 +16,14 @@
package android.app.ondeviceintelligence;
-import android.app.ondeviceintelligence.IProcessingSignal;
import android.os.PersistableBundle;
/**
- * Interface for Download callback to passed onto service implementation,
+ * Interface for Download callback to be passed onto service implementation,
*
* @hide
*/
-oneway interface IDownloadCallback {
+interface IDownloadCallback {
void onDownloadStarted(long bytesToDownload) = 1;
void onDownloadProgress(long bytesDownloaded) = 2;
void onDownloadFailed(int failureStatus, String errorMessage, in PersistableBundle errorParams) = 3;
diff --git a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
index 360a809..0dbe181 100644
--- a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
+++ b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
@@ -21,7 +21,7 @@
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
- import android.app.ondeviceintelligence.Content;
+ import android.os.Bundle;
import android.app.ondeviceintelligence.Feature;
import android.app.ondeviceintelligence.FeatureDetails;
import android.app.ondeviceintelligence.IDownloadCallback;
@@ -39,7 +39,7 @@
*
* @hide
*/
- oneway interface IOnDeviceIntelligenceManager {
+ interface IOnDeviceIntelligenceManager {
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
void getVersion(in RemoteCallback remoteCallback) = 1;
@@ -53,18 +53,20 @@
void getFeatureDetails(in Feature feature, in IFeatureDetailsCallback featureDetailsCallback) = 4;
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
- void requestFeatureDownload(in Feature feature, ICancellationSignal signal, in IDownloadCallback callback) = 5;
+ void requestFeatureDownload(in Feature feature, in ICancellationSignal signal, in IDownloadCallback callback) = 5;
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
- void requestTokenInfo(in Feature feature, in Content request, in ICancellationSignal signal,
+ void requestTokenInfo(in Feature feature, in Bundle requestBundle, in ICancellationSignal signal,
in ITokenInfoCallback tokenInfocallback) = 6;
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
- void processRequest(in Feature feature, in Content request, int requestType, in ICancellationSignal cancellationSignal, in IProcessingSignal signal,
- in IResponseCallback responseCallback) = 7;
+ void processRequest(in Feature feature, in Bundle requestBundle, int requestType, in ICancellationSignal cancellationSignal,
+ in IProcessingSignal signal, in IResponseCallback responseCallback) = 7;
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
void processRequestStreaming(in Feature feature,
- in Content request, int requestType, in ICancellationSignal cancellationSignal, in IProcessingSignal signal,
+ in Bundle requestBundle, int requestType, in ICancellationSignal cancellationSignal, in IProcessingSignal signal,
in IStreamingResponseCallback streamingCallback) = 8;
+
+ String getRemoteServicePackageName() = 9;
}
diff --git a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
index 0adf305..45963d2 100644
--- a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
+++ b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
@@ -1,8 +1,7 @@
package android.app.ondeviceintelligence;
-import android.app.ondeviceintelligence.Content;
-import android.app.ondeviceintelligence.IProcessingSignal;
import android.os.PersistableBundle;
+import android.os.Bundle;
import android.os.RemoteCallback;
/**
@@ -11,7 +10,7 @@
* @hide
*/
interface IResponseCallback {
- void onSuccess(in Content result) = 1;
+ void onSuccess(in Bundle resultBundle) = 1;
void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
- void onDataAugmentRequest(in Content content, in RemoteCallback contentCallback) = 3;
+ void onDataAugmentRequest(in Bundle processedContent, in RemoteCallback responseCallback) = 3;
}
diff --git a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
index 132e53e..671abe3 100644
--- a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
+++ b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
@@ -1,10 +1,8 @@
package android.app.ondeviceintelligence;
-import android.app.ondeviceintelligence.Content;
-import android.app.ondeviceintelligence.IResponseCallback;
-import android.app.ondeviceintelligence.IProcessingSignal;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
+import android.os.Bundle;
/**
@@ -13,8 +11,8 @@
* @hide
*/
interface IStreamingResponseCallback {
- void onNewContent(in Content result) = 1;
- void onSuccess(in Content result) = 2;
+ void onNewContent(in Bundle processedResult) = 1;
+ void onSuccess(in Bundle result) = 2;
void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 3;
- void onDataAugmentRequest(in Content content, in RemoteCallback contentCallback) = 4;
+ void onDataAugmentRequest(in Bundle processedContent, in RemoteCallback responseCallback) = 4;
}
diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java
new file mode 100644
index 0000000..03ff563a
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.ondeviceintelligence;
+
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.PersistableBundle;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * Exception type to be used for errors related to on-device intelligence system service with
+ * appropriate error code.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public class OnDeviceIntelligenceException extends Exception {
+
+ public static final int PROCESSING_ERROR_UNKNOWN = 1;
+
+ /** Request passed contains bad data for e.g. format. */
+ public static final int PROCESSING_ERROR_BAD_DATA = 2;
+
+ /** Bad request for inputs. */
+ public static final int PROCESSING_ERROR_BAD_REQUEST = 3;
+
+ /** Whole request was classified as not safe, and no response will be generated. */
+ public static final int PROCESSING_ERROR_REQUEST_NOT_SAFE = 4;
+
+ /** Underlying processing encountered an error and failed to compute results. */
+ public static final int PROCESSING_ERROR_COMPUTE_ERROR = 5;
+
+ /** Encountered an error while performing IPC */
+ public static final int PROCESSING_ERROR_IPC_ERROR = 6;
+
+ /** Request was cancelled either by user signal or by the underlying implementation. */
+ public static final int PROCESSING_ERROR_CANCELLED = 7;
+
+ /** Underlying processing in the remote implementation is not available. */
+ public static final int PROCESSING_ERROR_NOT_AVAILABLE = 8;
+
+ /** The service is currently busy. Callers should retry with exponential backoff. */
+ public static final int PROCESSING_ERROR_BUSY = 9;
+
+ /** Something went wrong with safety classification service. */
+ public static final int PROCESSING_ERROR_SAFETY_ERROR = 10;
+
+ /** Response generated was classified unsafe. */
+ public static final int PROCESSING_ERROR_RESPONSE_NOT_SAFE = 11;
+
+ /** Request is too large to be processed. */
+ public static final int PROCESSING_ERROR_REQUEST_TOO_LARGE = 12;
+
+ /** Inference suspended so that higher-priority inference can run. */
+ public static final int PROCESSING_ERROR_SUSPENDED = 13;
+
+ /**
+ * Underlying processing encountered an internal error, like a violated precondition
+ * .
+ */
+ public static final int PROCESSING_ERROR_INTERNAL = 14;
+
+ /**
+ * The processing was not able to be passed on to the remote implementation, as the
+ * service
+ * was unavailable.
+ */
+ public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15;
+ /**
+ * Error code returned when the OnDeviceIntelligenceManager service is unavailable.
+ */
+ public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 100;
+
+ /**
+ * The connection to remote service failed and the processing state could not be updated.
+ */
+ public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 200;
+
+
+ /**
+ * Error code associated with the on-device intelligence failure.
+ *
+ * @hide
+ */
+ @IntDef(
+ value = {
+ PROCESSING_ERROR_UNKNOWN,
+ PROCESSING_ERROR_BAD_DATA,
+ PROCESSING_ERROR_BAD_REQUEST,
+ PROCESSING_ERROR_REQUEST_NOT_SAFE,
+ PROCESSING_ERROR_COMPUTE_ERROR,
+ PROCESSING_ERROR_IPC_ERROR,
+ PROCESSING_ERROR_CANCELLED,
+ PROCESSING_ERROR_NOT_AVAILABLE,
+ PROCESSING_ERROR_BUSY,
+ PROCESSING_ERROR_SAFETY_ERROR,
+ PROCESSING_ERROR_RESPONSE_NOT_SAFE,
+ PROCESSING_ERROR_REQUEST_TOO_LARGE,
+ PROCESSING_ERROR_SUSPENDED,
+ PROCESSING_ERROR_INTERNAL,
+ PROCESSING_ERROR_SERVICE_UNAVAILABLE,
+ ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ PROCESSING_UPDATE_STATUS_CONNECTION_FAILED
+ }, open = true)
+ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ @interface OnDeviceIntelligenceError {
+ }
+
+ private final int mErrorCode;
+ private final PersistableBundle mErrorParams;
+
+ /** Returns the error code of the exception. */
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+
+ /** Returns the error params of the exception. */
+ @NonNull
+ public PersistableBundle getErrorParams() {
+ return mErrorParams;
+ }
+
+ /**
+ * Creates a new OnDeviceIntelligenceException with the specified error code, error message and
+ * error params.
+ *
+ * @param errorCode The error code.
+ * @param errorMessage The error message.
+ * @param errorParams The error params.
+ */
+ public OnDeviceIntelligenceException(
+ @OnDeviceIntelligenceError int errorCode, @NonNull String errorMessage,
+ @NonNull PersistableBundle errorParams) {
+ super(errorMessage);
+ this.mErrorCode = errorCode;
+ this.mErrorParams = errorParams;
+ }
+
+ /**
+ * Creates a new OnDeviceIntelligenceException with the specified error code and error params.
+ *
+ * @param errorCode The error code.
+ * @param errorParams The error params.
+ */
+ public OnDeviceIntelligenceException(
+ @OnDeviceIntelligenceError int errorCode,
+ @NonNull PersistableBundle errorParams) {
+ this.mErrorCode = errorCode;
+ this.mErrorParams = errorParams;
+ }
+
+ /**
+ * Creates a new OnDeviceIntelligenceException with the specified error code and error message.
+ *
+ * @param errorCode The error code.
+ * @param errorMessage The error message.
+ */
+ public OnDeviceIntelligenceException(
+ @OnDeviceIntelligenceError int errorCode, @NonNull String errorMessage) {
+ super(errorMessage);
+ this.mErrorCode = errorCode;
+ this.mErrorParams = new PersistableBundle();
+ }
+
+ /**
+ * Creates a new OnDeviceIntelligenceException with the specified error code.
+ *
+ * @param errorCode The error code.
+ */
+ public OnDeviceIntelligenceException(
+ @OnDeviceIntelligenceError int errorCode) {
+ this.mErrorCode = errorCode;
+ this.mErrorParams = new PersistableBundle();
+ }
+}
diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
index d195c4d..a465e3c 100644
--- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
+++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
@@ -28,6 +28,7 @@
import android.annotation.SystemService;
import android.content.ComponentName;
import android.content.Context;
+import android.graphics.Bitmap;
import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -36,6 +37,7 @@
import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.system.OsConstants;
import androidx.annotation.IntDef;
@@ -63,7 +65,7 @@
@SystemApi
@SystemService(Context.ON_DEVICE_INTELLIGENCE_SERVICE)
@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public class OnDeviceIntelligenceManager {
+public final class OnDeviceIntelligenceManager {
/**
* @hide
*/
@@ -118,14 +120,13 @@
@Nullable
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
public String getRemoteServicePackageName() {
- String serviceConfigValue = mContext.getResources().getString(
- R.string.config_defaultOnDeviceSandboxedInferenceService);
- ComponentName componentName = ComponentName.unflattenFromString(serviceConfigValue);
- if (componentName != null) {
- return componentName.getPackageName();
+ String result;
+ try{
+ result = mService.getRemoteServicePackageName();
+ } catch (RemoteException e){
+ throw e.rethrowFromSystemServer();
}
-
- return null;
+ return result;
}
/**
@@ -139,7 +140,7 @@
public void getFeature(
int featureId,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull OutcomeReceiver<Feature, OnDeviceIntelligenceManagerException> featureReceiver) {
+ @NonNull OutcomeReceiver<Feature, OnDeviceIntelligenceException> featureReceiver) {
try {
IFeatureCallback callback =
new IFeatureCallback.Stub() {
@@ -154,7 +155,7 @@
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> featureReceiver.onError(
- new OnDeviceIntelligenceManagerException(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams))));
}
};
@@ -173,7 +174,7 @@
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
public void listFeatures(
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull OutcomeReceiver<List<Feature>, OnDeviceIntelligenceManagerException> featureListReceiver) {
+ @NonNull OutcomeReceiver<List<Feature>, OnDeviceIntelligenceException> featureListReceiver) {
try {
IListFeaturesCallback callback =
new IListFeaturesCallback.Stub() {
@@ -188,7 +189,7 @@
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> featureListReceiver.onError(
- new OnDeviceIntelligenceManagerException(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams))));
}
};
@@ -211,7 +212,7 @@
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
public void getFeatureDetails(@NonNull Feature feature,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull OutcomeReceiver<FeatureDetails, OnDeviceIntelligenceManagerException> featureDetailsReceiver) {
+ @NonNull OutcomeReceiver<FeatureDetails, OnDeviceIntelligenceException> featureDetailsReceiver) {
try {
IFeatureDetailsCallback callback = new IFeatureDetailsCallback.Stub() {
@@ -226,7 +227,7 @@
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> featureDetailsReceiver.onError(
- new OnDeviceIntelligenceManagerException(errorCode,
+ new OnDeviceIntelligenceException(errorCode,
errorMessage, errorParams))));
}
};
@@ -243,9 +244,8 @@
*
* Note: If a feature was already requested for downloaded previously, the onDownloadFailed
* callback would be invoked with {@link DownloadCallback#DOWNLOAD_FAILURE_STATUS_DOWNLOADING}.
- * In such cases, clients should query the feature status via {@link #getFeatureStatus} to
- * check
- * on the feature's download status.
+ * In such cases, clients should query the feature status via {@link #getFeatureDetails} to
+ * check on the feature's download status.
*
* @param feature feature to request download for.
* @param callback callback to populate updates about download status.
@@ -284,7 +284,7 @@
@Override
public void onDownloadCompleted(PersistableBundle downloadParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
- () -> onDownloadCompleted(downloadParams)));
+ () -> callback.onDownloadCompleted(downloadParams)));
}
};
@@ -305,7 +305,8 @@
* provided {@link Feature}.
*
* @param feature feature associated with the request.
- * @param request request that contains the content data and associated params.
+ * @param request request and associated params represented by the Bundle
+ * data.
* @param outcomeReceiver callback to populate the token info or exception in case of
* failure.
* @param cancellationSignal signal to invoke cancellation on the operation in the remote
@@ -313,11 +314,11 @@
* @param callbackExecutor executor to run the callback on.
*/
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
- public void requestTokenInfo(@NonNull Feature feature, @NonNull Content request,
+ public void requestTokenInfo(@NonNull Feature feature, @NonNull @InferenceParams Bundle request,
@Nullable CancellationSignal cancellationSignal,
@NonNull @CallbackExecutor Executor callbackExecutor,
@NonNull OutcomeReceiver<TokenInfo,
- OnDeviceIntelligenceManagerException> outcomeReceiver) {
+ OnDeviceIntelligenceException> outcomeReceiver) {
try {
ITokenInfoCallback callback = new ITokenInfoCallback.Stub() {
@Override
@@ -331,7 +332,7 @@
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> outcomeReceiver.onError(
- new OnDeviceIntelligenceManagerProcessingException(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams))));
}
};
@@ -357,30 +358,30 @@
* failure.
*
* @param feature feature associated with the request.
- * @param request request that contains the Content data and
- * associated params.
+ * @param request request and associated params represented by the Bundle
+ * data.
* @param requestType type of request being sent for processing the content.
* @param cancellationSignal signal to invoke cancellation.
* @param processingSignal signal to send custom signals in the
* remote implementation.
* @param callbackExecutor executor to run the callback on.
- * @param responseCallback callback to populate the response content and
+ * @param processingCallback callback to populate the response content and
* associated params.
*/
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
- public void processRequest(@NonNull Feature feature, @Nullable Content request,
+ public void processRequest(@NonNull Feature feature, @NonNull @InferenceParams Bundle request,
@RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull ProcessingOutcomeReceiver responseCallback) {
+ @NonNull ProcessingCallback processingCallback) {
try {
IResponseCallback callback = new IResponseCallback.Stub() {
@Override
- public void onSuccess(Content result) {
+ public void onSuccess(@InferenceParams Bundle result) {
Binder.withCleanCallingIdentity(() -> {
- callbackExecutor.execute(() -> responseCallback.onResult(result));
+ callbackExecutor.execute(() -> processingCallback.onResult(result));
});
}
@@ -388,16 +389,16 @@
public void onFailure(int errorCode, String errorMessage,
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
- () -> responseCallback.onError(
- new OnDeviceIntelligenceManagerProcessingException(
+ () -> processingCallback.onError(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams))));
}
@Override
- public void onDataAugmentRequest(@NonNull Content content,
+ public void onDataAugmentRequest(@NonNull @InferenceParams Bundle request,
@NonNull RemoteCallback contentCallback) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
- () -> responseCallback.onDataAugmentRequest(content, result -> {
+ () -> processingCallback.onDataAugmentRequest(request, result -> {
Bundle bundle = new Bundle();
bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY, result);
callbackExecutor.execute(() -> contentCallback.sendResult(bundle));
@@ -430,15 +431,15 @@
* Variation of {@link #processRequest} that asynchronously processes a request in a
* streaming
* fashion, where new content is pushed to caller in chunks via the
- * {@link StreamedProcessingOutcomeReceiver#onNewContent}. After the streaming is complete,
- * the service should call {@link StreamedProcessingOutcomeReceiver#onResult} and can optionally
- * populate the complete the full response {@link Content} as part of the callback in cases
- * when the final response contains an enhanced aggregation of the Contents already
+ * {@link StreamingProcessingCallback#onPartialResult}. After the streaming is complete,
+ * the service should call {@link StreamingProcessingCallback#onResult} and can optionally
+ * populate the complete the full response {@link Bundle} as part of the callback in cases
+ * when the final response contains an enhanced aggregation of the contents already
* streamed.
*
* @param feature feature associated with the request.
- * @param request request that contains the Content data and associated
- * params.
+ * @param request request and associated params represented by the Bundle
+ * data.
* @param requestType type of request being sent for processing the content.
* @param cancellationSignal signal to invoke cancellation.
* @param processingSignal signal to send custom signals in the
@@ -448,27 +449,27 @@
* @param callbackExecutor executor to run the callback on.
*/
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
- public void processRequestStreaming(@NonNull Feature feature, @Nullable Content request,
+ public void processRequestStreaming(@NonNull Feature feature, @NonNull @InferenceParams Bundle request,
@RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull StreamedProcessingOutcomeReceiver streamingResponseCallback) {
+ @NonNull StreamingProcessingCallback streamingProcessingCallback) {
try {
IStreamingResponseCallback callback = new IStreamingResponseCallback.Stub() {
@Override
- public void onNewContent(Content result) {
+ public void onNewContent(@InferenceParams Bundle result) {
Binder.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
- () -> streamingResponseCallback.onNewContent(result));
+ () -> streamingProcessingCallback.onPartialResult(result));
});
}
@Override
- public void onSuccess(Content result) {
+ public void onSuccess(@InferenceParams Bundle result) {
Binder.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
- () -> streamingResponseCallback.onResult(result));
+ () -> streamingProcessingCallback.onResult(result));
});
}
@@ -477,18 +478,18 @@
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
- () -> streamingResponseCallback.onError(
- new OnDeviceIntelligenceManagerProcessingException(
+ () -> streamingProcessingCallback.onError(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams)));
});
}
@Override
- public void onDataAugmentRequest(@NonNull Content content,
+ public void onDataAugmentRequest(@NonNull @InferenceParams Bundle content,
@NonNull RemoteCallback contentCallback) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
- () -> streamingResponseCallback.onDataAugmentRequest(content,
+ () -> streamingProcessingCallback.onDataAugmentRequest(content,
contentResponse -> {
Bundle bundle = new Bundle();
bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY,
@@ -519,7 +520,7 @@
}
- /** Request inference with provided Content and Params. */
+ /** Request inference with provided Bundle and Params. */
public static final int REQUEST_TYPE_INFERENCE = 0;
/**
@@ -530,7 +531,7 @@
*/
public static final int REQUEST_TYPE_PREPARE = 1;
- /** Request Embeddings of the passed-in Content. */
+ /** Request Embeddings of the passed-in Bundle. */
public static final int REQUEST_TYPE_EMBEDDINGS = 2;
/**
@@ -547,154 +548,30 @@
public @interface RequestType {
}
-
/**
- * Exception type to be populated in callbacks to the methods under
- * {@link OnDeviceIntelligenceManager}.
+ * {@link Bundle}s annotated with this type will be validated that they are in-effect read-only
+ * when passed to inference service via Binder IPC. Following restrictions apply :
+ * <ul>
+ * <li> Any primitive types or their collections can be added as usual.</li>
+ * <li>IBinder objects should *not* be added.</li>
+ * <li>Parcelable data which has no active-objects, should be added as
+ * {@link Bundle#putByteArray}</li>
+ * <li>Parcelables have active-objects, only following types will be allowed</li>
+ * <ul>
+ * <li>{@link Bitmap} set as {@link Bitmap#setImmutable()}</li>
+ * <li>{@link android.database.CursorWindow}</li>
+ * <li>{@link android.os.ParcelFileDescriptor} opened in
+ * {@link android.os.ParcelFileDescriptor#MODE_READ_ONLY}</li>
+ * <li>{@link android.os.SharedMemory} set to {@link OsConstants#PROT_READ}</li>
+ * </ul>
+ * </ul>
+ *
+ * In all other scenarios the system-server might throw a
+ * {@link android.os.BadParcelableException} if the Bundle validation fails.
+ *
+ * @hide
*/
- public static class OnDeviceIntelligenceManagerException extends Exception {
- /**
- * Error code returned when the OnDeviceIntelligenceManager service is unavailable.
- */
- public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 1000;
-
- /**
- * Error code to be used for on device intelligence manager failures.
- *
- * @hide
- */
- @IntDef(
- value = {
- ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE
- }, open = true)
- @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
- @interface OnDeviceIntelligenceManagerErrorCode {
- }
-
- private final int mErrorCode;
- private final PersistableBundle errorParams;
-
- public OnDeviceIntelligenceManagerException(
- @OnDeviceIntelligenceManagerErrorCode int errorCode, @NonNull String errorMessage,
- @NonNull PersistableBundle errorParams) {
- super(errorMessage);
- this.mErrorCode = errorCode;
- this.errorParams = errorParams;
- }
-
- public OnDeviceIntelligenceManagerException(
- @OnDeviceIntelligenceManagerErrorCode int errorCode,
- @NonNull PersistableBundle errorParams) {
- this.mErrorCode = errorCode;
- this.errorParams = errorParams;
- }
-
- public int getErrorCode() {
- return mErrorCode;
- }
-
- @NonNull
- public PersistableBundle getErrorParams() {
- return errorParams;
- }
- }
-
- /**
- * Exception type to be populated in callbacks to the methods under
- * {@link OnDeviceIntelligenceManager#processRequest} or
- * {@link OnDeviceIntelligenceManager#processRequestStreaming} .
- */
- public static class OnDeviceIntelligenceManagerProcessingException extends
- OnDeviceIntelligenceManagerException {
-
- public static final int PROCESSING_ERROR_UNKNOWN = 1;
-
- /** Request passed contains bad data for e.g. format. */
- public static final int PROCESSING_ERROR_BAD_DATA = 2;
-
- /** Bad request for inputs. */
- public static final int PROCESSING_ERROR_BAD_REQUEST = 3;
-
- /** Whole request was classified as not safe, and no response will be generated. */
- public static final int PROCESSING_ERROR_REQUEST_NOT_SAFE = 4;
-
- /** Underlying processing encountered an error and failed to compute results. */
- public static final int PROCESSING_ERROR_COMPUTE_ERROR = 5;
-
- /** Encountered an error while performing IPC */
- public static final int PROCESSING_ERROR_IPC_ERROR = 6;
-
- /** Request was cancelled either by user signal or by the underlying implementation. */
- public static final int PROCESSING_ERROR_CANCELLED = 7;
-
- /** Underlying processing in the remote implementation is not available. */
- public static final int PROCESSING_ERROR_NOT_AVAILABLE = 8;
-
- /** The service is currently busy. Callers should retry with exponential backoff. */
- public static final int PROCESSING_ERROR_BUSY = 9;
-
- /** Something went wrong with safety classification service. */
- public static final int PROCESSING_ERROR_SAFETY_ERROR = 10;
-
- /** Response generated was classified unsafe. */
- public static final int PROCESSING_ERROR_RESPONSE_NOT_SAFE = 11;
-
- /** Request is too large to be processed. */
- public static final int PROCESSING_ERROR_REQUEST_TOO_LARGE = 12;
-
- /** Inference suspended so that higher-priority inference can run. */
- public static final int PROCESSING_ERROR_SUSPENDED = 13;
-
- /**
- * Underlying processing encountered an internal error, like a violated precondition
- * .
- */
- public static final int PROCESSING_ERROR_INTERNAL = 14;
-
- /**
- * The processing was not able to be passed on to the remote implementation, as the
- * service
- * was unavailable.
- */
- public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15;
-
- /**
- * Error code of failed processing request.
- *
- * @hide
- */
- @IntDef(
- value = {
- PROCESSING_ERROR_UNKNOWN,
- PROCESSING_ERROR_BAD_DATA,
- PROCESSING_ERROR_BAD_REQUEST,
- PROCESSING_ERROR_REQUEST_NOT_SAFE,
- PROCESSING_ERROR_COMPUTE_ERROR,
- PROCESSING_ERROR_IPC_ERROR,
- PROCESSING_ERROR_CANCELLED,
- PROCESSING_ERROR_NOT_AVAILABLE,
- PROCESSING_ERROR_BUSY,
- PROCESSING_ERROR_SAFETY_ERROR,
- PROCESSING_ERROR_RESPONSE_NOT_SAFE,
- PROCESSING_ERROR_REQUEST_TOO_LARGE,
- PROCESSING_ERROR_SUSPENDED,
- PROCESSING_ERROR_INTERNAL,
- PROCESSING_ERROR_SERVICE_UNAVAILABLE
- }, open = true)
- @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
- @interface ProcessingError {
- }
-
- public OnDeviceIntelligenceManagerProcessingException(
- @ProcessingError int errorCode, @NonNull String errorMessage,
- @NonNull PersistableBundle errorParams) {
- super(errorCode, errorMessage, errorParams);
- }
-
- public OnDeviceIntelligenceManagerProcessingException(
- @ProcessingError int errorCode,
- @NonNull PersistableBundle errorParams) {
- super(errorCode, errorParams);
- }
+ @Target({ElementType.PARAMETER, ElementType.FIELD})
+ public @interface InferenceParams {
}
}
diff --git a/core/java/android/app/ondeviceintelligence/ProcessingCallback.java b/core/java/android/app/ondeviceintelligence/ProcessingCallback.java
new file mode 100644
index 0000000..4d936ea
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/ProcessingCallback.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.InferenceParams;
+
+import java.util.function.Consumer;
+
+/**
+ * Callback to populate the processed response or any error that occurred during the
+ * request processing. This callback also provides a method to request additional data to be
+ * augmented to the request-processing, using the partial response that was already
+ * processed in the remote implementation.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public interface ProcessingCallback {
+ /**
+ * Invoked when request has been processed and result is ready to be propagated to the
+ * caller.
+ *
+ * @param result Response to be passed as a result.
+ */
+ void onResult(@NonNull @InferenceParams Bundle result);
+
+ /**
+ * Called when the request processing fails. The failure details are indicated by the
+ * {@link OnDeviceIntelligenceException} passed as an argument to this method.
+ *
+ * @param error An exception with more details about the error that occurred.
+ */
+ void onError(@NonNull OnDeviceIntelligenceException error);
+
+ /**
+ * Callback to be invoked in cases where the remote service needs to perform retrieval or
+ * transformation operations based on a partially processed request, in order to augment the
+ * final response, by using the additional context sent via this callback.
+ *
+ * @param processedContent The content payload that should be used to augment ongoing request.
+ * @param contentConsumer The augmentation data that should be sent to remote
+ * service for further processing a request. Bundle passed in here is
+ * expected to be non-null or EMPTY when there is no response.
+ */
+ default void onDataAugmentRequest(
+ @NonNull @InferenceParams Bundle processedContent,
+ @NonNull Consumer<Bundle> contentConsumer) {
+ contentConsumer.accept(Bundle.EMPTY);
+ }
+}
diff --git a/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java b/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java
deleted file mode 100644
index b0b6e19..0000000
--- a/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.ondeviceintelligence;
-
-import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
-
-import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
-import android.annotation.SystemApi;
-import android.os.OutcomeReceiver;
-
-import java.util.function.Consumer;
-
-/**
- * Response Callback to populate the processed response or any error that occurred during the
- * request processing. This callback also provides a method to request additional data to be
- * augmented to the request-processing, using the partial {@link Content} that was already
- * processed in the remote implementation.
- *
- * @hide
- */
-@SystemApi
-@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public interface ProcessingOutcomeReceiver extends
- OutcomeReceiver<Content,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> {
- /**
- * Callback to be invoked in cases where the remote service needs to perform retrieval or
- * transformation operations based on a partially processed request, in order to augment the
- * final response, by using the additional context sent via this callback.
- *
- * @param content The content payload that should be used to augment ongoing request.
- * @param contentConsumer The augmentation data that should be sent to remote
- * service for further processing a request.
- */
- default void onDataAugmentRequest(@NonNull Content content,
- @NonNull Consumer<Content> contentConsumer) {
- contentConsumer.accept(null);
- }
-}
diff --git a/core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java b/core/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java
similarity index 65%
rename from core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java
rename to core/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java
index ac2b032..41f1807 100644
--- a/core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java
+++ b/core/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java
@@ -21,19 +21,21 @@
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.InferenceParams;
/**
- * Streaming variant of outcome receiver to populate response while processing a given request,
- * possibly in chunks to provide a async processing behaviour to the caller.
+ * Streaming variant of {@link ProcessingCallback} to populate response while processing a given
+ * request, possibly in chunks to provide a async processing behaviour to the caller.
*
* @hide
*/
@SystemApi
@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public interface StreamedProcessingOutcomeReceiver extends ProcessingOutcomeReceiver {
+public interface StreamingProcessingCallback extends ProcessingCallback {
/**
- * Callback that would be invoked when a part of the response i.e. some {@link Content} is
- * already processed and needs to be passed onto the caller.
+ * Callback that would be invoked when a part of the response i.e. some response is
+ * already processed, and needs to be passed onto the caller.
*/
- void onNewContent(@NonNull Content content);
+ void onPartialResult(@NonNull @InferenceParams Bundle partialResult);
}
diff --git a/core/java/android/app/usage/OWNERS b/core/java/android/app/usage/OWNERS
index a4bf985..57d958f 100644
--- a/core/java/android/app/usage/OWNERS
+++ b/core/java/android/app/usage/OWNERS
@@ -3,6 +3,7 @@
yamasani@google.com
mwachens@google.com
varunshah@google.com
+guanxin@google.com
per-file *StorageStats* = file:/core/java/android/os/storage/OWNERS
per-file *Broadcast* = sudheersai@google.com
diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java
index fd72c49..9573e6f 100644
--- a/core/java/android/app/wearable/WearableSensingManager.java
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -95,11 +95,12 @@
/**
* The value of the status code that indicates one or more of the requested events are not
* supported.
+ *
+ * @deprecated WearableSensingManager does not deal with events. Use {@link
+ * STATUS_UNSUPPORTED_OPERATION} instead for operations not supported by the implementation of
+ * {@link WearableSensingService}.
*/
- // TODO(b/324635656): Deprecate this status code. Update Javadoc:
- // @deprecated WearableSensingManager does not deal with events. Use {@link
- // STATUS_UNSUPPORTED_OPERATION} instead for operations not supported by the implementation of
- // {@link WearableSensingService}.
+ @Deprecated
public static final int STATUS_UNSUPPORTED = 2;
/**
@@ -121,7 +122,6 @@
* The value of the status code that indicates the method called is not supported by the
* implementation of {@link WearableSensingService}.
*/
-
@FlaggedApi(Flags.FLAG_ENABLE_UNSUPPORTED_OPERATION_STATUS_CODE)
public static final int STATUS_UNSUPPORTED_OPERATION = 6;
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index cda4d89..2c0e035 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -822,7 +822,18 @@
*
* @param appWidgetIds The AppWidget instances to notify of view data changes.
* @param viewId The collection view id.
+ * @deprecated The corresponding API
+ * {@link RemoteViews#setRemoteAdapter(int, Intent)} associated with this method has been
+ * deprecated. Moving forward please use
+ * {@link RemoteViews#setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)}
+ * instead to set {@link android.widget.RemoteViews.RemoteCollectionItems} for the remote
+ * adapter and update the widget views by calling {@link #updateAppWidget(int[], RemoteViews)},
+ * {@link #updateAppWidget(int, RemoteViews)},
+ * {@link #updateAppWidget(ComponentName, RemoteViews)},
+ * {@link #partiallyUpdateAppWidget(int[], RemoteViews)},
+ * or {@link #partiallyUpdateAppWidget(int, RemoteViews)}, whichever applicable.
*/
+ @Deprecated
public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) {
if (mService == null) {
return;
@@ -873,7 +884,18 @@
*
* @param appWidgetId The AppWidget instance to notify of view data changes.
* @param viewId The collection view id.
+ * @deprecated The corresponding API
+ * {@link RemoteViews#setRemoteAdapter(int, Intent)} associated with this method has been
+ * deprecated. Moving forward please use
+ * {@link RemoteViews#setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)}
+ * instead to set {@link android.widget.RemoteViews.RemoteCollectionItems} for the remote
+ * adapter and update the widget views by calling {@link #updateAppWidget(int[], RemoteViews)},
+ * {@link #updateAppWidget(int, RemoteViews)},
+ * {@link #updateAppWidget(ComponentName, RemoteViews)},
+ * {@link #partiallyUpdateAppWidget(int[], RemoteViews)},
+ * or {@link #partiallyUpdateAppWidget(int, RemoteViews)}, whichever applicable.
*/
+ @Deprecated
public void notifyAppWidgetViewDataChanged(int appWidgetId, int viewId) {
if (mService == null) {
return;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index c7e5d88..7f2ec53 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5067,6 +5067,7 @@
* {@link android.hardware.fingerprint.FingerprintManager} for handling management
* of fingerprints.
*
+ * @removed See {@link android.hardware.biometrics.BiometricPrompt}
* @see #getSystemService(String)
* @see android.hardware.fingerprint.FingerprintManager
*/
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 42dd87a..443aadd 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -8183,7 +8183,7 @@
// launch flags
else if (uri.startsWith("launchFlags=", i)) {
- intent.mFlags = Integer.decode(value);
+ intent.mFlags = decodeInteger(value);
if ((flags& URI_ALLOW_UNSAFE) == 0) {
intent.mFlags &= ~IMMUTABLE_FLAGS;
}
@@ -8191,7 +8191,7 @@
// extended flags
else if (uri.startsWith("extendedLaunchFlags=", i)) {
- intent.mExtendedFlags = Integer.decode(value);
+ intent.mExtendedFlags = decodeInteger(value);
}
// package
@@ -8401,7 +8401,7 @@
isIntentFragment = true;
i += 12;
int j = uri.indexOf(')', i);
- intent.mFlags = Integer.decode(uri.substring(i, j));
+ intent.mFlags = decodeInteger(uri.substring(i, j));
if ((flags& URI_ALLOW_UNSAFE) == 0) {
intent.mFlags &= ~IMMUTABLE_FLAGS;
}
@@ -8512,6 +8512,23 @@
return intent;
}
+ private static Integer decodeInteger(String value) {
+ try {
+ return Integer.decode(value);
+ } catch (NumberFormatException e) {
+ try {
+ if (value != null && value.startsWith("0x")) {
+ // In toUriInner, we do "0x".append(Integer.toHexString).
+ // Sometimes "decode" fails to parse, e.g. 0x90000000.
+ return Integer.parseUnsignedInt(value.substring(2), 16);
+ }
+ } catch (NumberFormatException ignored) {
+ // ignored, throw the original exception
+ }
+ throw e;
+ }
+ }
+
/** @hide */
public interface CommandOptionHandler {
boolean handleOption(String opt, ShellCommand cmd);
@@ -8577,7 +8594,7 @@
case "--ei": {
String key = cmd.getNextArgRequired();
String value = cmd.getNextArgRequired();
- intent.putExtra(key, Integer.decode(value));
+ intent.putExtra(key, decodeInteger(value));
}
break;
case "--eu": {
@@ -8601,7 +8618,7 @@
String[] strings = value.split(",");
int[] list = new int[strings.length];
for (int i = 0; i < strings.length; i++) {
- list[i] = Integer.decode(strings[i]);
+ list[i] = decodeInteger(strings[i]);
}
intent.putExtra(key, list);
}
@@ -8612,7 +8629,7 @@
String[] strings = value.split(",");
ArrayList<Integer> list = new ArrayList<>(strings.length);
for (int i = 0; i < strings.length; i++) {
- list.add(Integer.decode(strings[i]));
+ list.add(decodeInteger(strings[i]));
}
intent.putExtra(key, list);
}
@@ -8747,7 +8764,7 @@
arg = false;
} else {
try {
- arg = Integer.decode(value) != 0;
+ arg = decodeInteger(value) != 0;
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("Invalid boolean value: " + value);
}
@@ -8777,7 +8794,7 @@
break;
case "-f":
String str = cmd.getNextArgRequired();
- intent.setFlags(Integer.decode(str).intValue());
+ intent.setFlags(decodeInteger(str).intValue());
break;
case "--grant-read-uri-permission":
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index a64ee5b..bd04634 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -333,10 +333,9 @@
}
/**
- * Specifies permissions necessary to launch this activity via
- * {@link android.content.Context#startActivity} when passing content URIs. The default value is
- * {@code none}, meaning no specific permissions are required. Setting this attribute restricts
- * activity invocation based on the invoker's permissions.
+ * Specifies permissions necessary to launch this activity when passing content URIs. The
+ * default value is {@code none}, meaning no specific permissions are required. Setting this
+ * attribute restricts activity invocation based on the invoker's permissions.
* @hide
*/
@RequiredContentUriPermission
@@ -1519,6 +1518,16 @@
private static final long CHECK_MIN_WIDTH_HEIGHT_FOR_MULTI_WINDOW = 197654537L;
/**
+ * The activity is targeting a SDK version that should receive the changed behavior of
+ * configuration insets decouple.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public static final long INSETS_DECOUPLED_CONFIGURATION_ENFORCED = 151861875L;
+
+ /**
* Optional set of a certificates identifying apps that are allowed to embed this activity. From
* the "knownActivityEmbeddingCerts" attribute.
*/
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 533fa51..55957bf 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -133,4 +133,7 @@
void setArchiveCompatibilityOptions(boolean enableIconOverlay, boolean enableUnarchivalConfirmation);
List<UserHandle> getUserProfiles();
+
+ /** Saves view capture data to the wm trace directory. */
+ void saveViewCaptureData();
}
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 41c1f17..3a5383d 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -1328,6 +1328,19 @@
}
/**
+ * Saves view capture data to the default location.
+ * @hide
+ */
+ @RequiresPermission(READ_FRAME_BUFFER)
+ public void saveViewCaptureData() {
+ try {
+ mService.saveViewCaptureData();
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
* Unregister a callback, so that it won't be called when LauncherApps dumps.
* @hide
*/
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 610057b..92cb9cc 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -46,6 +46,7 @@
flag {
name: "use_art_service_v2"
+ is_exported: true
namespace: "package_manager_service"
description: "Feature flag to enable the features that rely on new ART Service APIs that are in the VIC version of the ART module."
bug: "304741685"
@@ -61,6 +62,7 @@
flag {
name: "rollback_lifetime"
+ is_exported: true
namespace: "package_manager_service"
description: "Feature flag to enable custom rollback lifetime during install."
bug: "299670324"
@@ -156,6 +158,7 @@
flag {
name: "recoverability_detection"
+ is_exported: true
namespace: "package_manager_service"
description: "Feature flag to enable recoverability detection feature. It includes GMS core rollback and improvements to rescue party."
bug: "291135724"
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index f660770..7fd0b03 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -38,7 +38,7 @@
name: "nine_patch_frro"
namespace: "resource_manager"
description: "Feature flag for creating an frro from a 9-patch"
- bug: "309232726"
+ bug: "296324826"
}
flag {
diff --git a/core/java/android/content/rollback/OWNERS b/core/java/android/content/rollback/OWNERS
index 8e5a0d8..c328b7c 100644
--- a/core/java/android/content/rollback/OWNERS
+++ b/core/java/android/content/rollback/OWNERS
@@ -1,5 +1,3 @@
# Bug component: 819107
-ancr@google.com
-harshitmahajan@google.com
-robertogil@google.com
+include /services/core/java/com/android/server/crashrecovery/OWNERS
\ No newline at end of file
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index faa2c70..dfb77c0 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -34,17 +34,22 @@
import android.util.LruCache;
import android.util.Pair;
import android.util.Printer;
+import com.android.internal.util.RingBuffer;
import dalvik.system.BlockGuard;
import dalvik.system.CloseGuard;
+
import java.io.File;
import java.io.IOException;
import java.lang.ref.Reference;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
+import java.util.Locale;
import java.util.Map;
import java.util.function.BinaryOperator;
import java.util.function.UnaryOperator;
@@ -185,7 +190,7 @@
SQLiteDatabaseConfiguration configuration,
int connectionId, boolean primaryConnection) {
mPool = pool;
- mRecentOperations = new OperationLog(mPool);
+ mRecentOperations = new OperationLog();
mConfiguration = new SQLiteDatabaseConfiguration(configuration);
mConnectionId = connectionId;
mIsPrimaryConnection = primaryConnection;
@@ -307,6 +312,16 @@
}
}
+ /** Record the start of a transaction for logging and debugging. */
+ void recordBeginTransaction(String mode) {
+ mRecentOperations.beginTransaction(mode);
+ }
+
+ /** Record the end of a transaction for logging and debugging. */
+ void recordEndTransaction(boolean successful) {
+ mRecentOperations.endTransaction(successful);
+ }
+
private void setPageSize() {
if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
final long newValue = SQLiteGlobal.getDefaultPageSize();
@@ -1337,6 +1352,7 @@
}
printer.println(" isPrimaryConnection: " + mIsPrimaryConnection);
printer.println(" onlyAllowReadOnlyOperations: " + mOnlyAllowReadOnlyOperations);
+ printer.println(" totalLongOperations: " + mRecentOperations.getTotalLongOperations());
mRecentOperations.dump(printer);
@@ -1595,51 +1611,39 @@
}
}
- private static final class OperationLog {
+ private final class OperationLog {
private static final int MAX_RECENT_OPERATIONS = 20;
private static final int COOKIE_GENERATION_SHIFT = 8;
private static final int COOKIE_INDEX_MASK = 0xff;
+ // Operations over 2s are long. Save the last ten.
+ private static final long LONG_OPERATION_THRESHOLD_MS = 2_000;
+ private static final int MAX_LONG_OPERATIONS = 10;
+
private final Operation[] mOperations = new Operation[MAX_RECENT_OPERATIONS];
- private int mIndex;
- private int mGeneration;
- private final SQLiteConnectionPool mPool;
+ private int mIndex = -1;
+ private int mGeneration = 0;
+ private final Operation mTransaction = new Operation();
private long mResultLong = Long.MIN_VALUE;
private String mResultString;
- OperationLog(SQLiteConnectionPool pool) {
- mPool = pool;
- }
+ private final RingBuffer<Operation> mLongOperations =
+ new RingBuffer<>(()->{return new Operation();},
+ (n) ->{return new Operation[n];},
+ MAX_LONG_OPERATIONS);
+ private int mTotalLongOperations = 0;
public int beginOperation(String kind, String sql, Object[] bindArgs) {
mResultLong = Long.MIN_VALUE;
mResultString = null;
synchronized (mOperations) {
- final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS;
- Operation operation = mOperations[index];
- if (operation == null) {
- operation = new Operation();
- mOperations[index] = operation;
- } else {
- operation.mFinished = false;
- operation.mException = null;
- if (operation.mBindArgs != null) {
- operation.mBindArgs.clear();
- }
- }
- operation.mStartWallTime = System.currentTimeMillis();
- operation.mStartTime = SystemClock.uptimeMillis();
+ Operation operation = newOperationLocked();
operation.mKind = kind;
operation.mSql = sql;
- operation.mPath = mPool.getPath();
- operation.mResultLong = Long.MIN_VALUE;
- operation.mResultString = null;
if (bindArgs != null) {
if (operation.mBindArgs == null) {
operation.mBindArgs = new ArrayList<Object>();
- } else {
- operation.mBindArgs.clear();
}
for (int i = 0; i < bindArgs.length; i++) {
final Object arg = bindArgs[i];
@@ -1651,16 +1655,44 @@
}
}
}
- operation.mCookie = newOperationCookieLocked(index);
if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
operation.mCookie);
}
- mIndex = index;
return operation.mCookie;
}
}
+ public void beginTransaction(String kind) {
+ synchronized (mOperations) {
+ Operation operation = newOperationLocked();
+ operation.mKind = kind;
+ mTransaction.copyFrom(operation);
+
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_DATABASE)) {
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
+ operation.mCookie);
+ }
+ }
+ }
+
+ /**
+ * Fetch a new operation from the ring buffer. The operation is properly initialized.
+ * This advances mIndex to point to the next element.
+ */
+ private Operation newOperationLocked() {
+ final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS;
+ Operation operation = mOperations[index];
+ if (operation == null) {
+ mOperations[index] = new Operation();
+ operation = mOperations[index];
+ }
+ operation.start();
+ operation.mCookie = newOperationCookieLocked(index);
+ mIndex = index;
+ return operation;
+ }
+
public void failOperation(int cookie, Exception ex) {
synchronized (mOperations) {
final Operation operation = getOperationLocked(cookie);
@@ -1684,6 +1716,20 @@
}
}
+ public boolean endTransaction(boolean success) {
+ synchronized (mOperations) {
+ mTransaction.mResultLong = success ? 1 : 0;
+ final long execTime = finishOperationLocked(mTransaction);
+ final Operation operation = getOperationLocked(mTransaction.mCookie);
+ if (operation != null) {
+ operation.copyFrom(mTransaction);
+ }
+ mTransaction.setEmpty();
+ return NoPreloadHolder.DEBUG_LOG_SLOW_QUERIES
+ && SQLiteDebug.shouldLogSlowQuery(execTime);
+ }
+ }
+
public void logOperation(int cookie, String detail) {
synchronized (mOperations) {
logOperationLocked(cookie, detail);
@@ -1705,9 +1751,7 @@
Trace.asyncTraceEnd(Trace.TRACE_TAG_DATABASE, operation.getTraceMethodName(),
operation.mCookie);
}
- operation.mEndTime = SystemClock.uptimeMillis();
- operation.mFinished = true;
- final long execTime = operation.mEndTime - operation.mStartTime;
+ final long execTime = finishOperationLocked(operation);
mPool.onStatementExecuted(execTime);
return NoPreloadHolder.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery(
execTime);
@@ -1732,10 +1776,22 @@
return generation << COOKIE_GENERATION_SHIFT | index;
}
+ /** Close out the operation and return the elapsed time. */
+ private long finishOperationLocked(Operation operation) {
+ operation.mEndTime = SystemClock.uptimeMillis();
+ operation.mFinished = true;
+ final long elapsed = operation.mEndTime - operation.mStartTime;
+ if (elapsed > LONG_OPERATION_THRESHOLD_MS) {
+ mLongOperations.getNextSlot().copyFrom(operation);
+ mTotalLongOperations++;
+ }
+ return elapsed;
+ }
+
private Operation getOperationLocked(int cookie) {
final int index = cookie & COOKIE_INDEX_MASK;
final Operation operation = mOperations[index];
- return operation.mCookie == cookie ? operation : null;
+ return (operation != null && operation.mCookie == cookie) ? operation : null;
}
public String describeCurrentOperation() {
@@ -1750,48 +1806,87 @@
}
}
- public void dump(Printer printer) {
+ /**
+ * Dump an Operation if it is not in the recent operations list. Return 1 if the
+ * operation was dumped and 0 if not.
+ */
+ private int dumpIfNotRecentLocked(Printer pw, Operation op, int counter) {
+ if (op == null || op.isEmpty() || getOperationLocked(op.mCookie) != null) {
+ return 0;
+ }
+ pw.println(op.describe(counter));
+ return 1;
+ }
+
+ private void dumpRecentLocked(Printer printer) {
synchronized (mOperations) {
printer.println(" Most recently executed operations:");
int index = mIndex;
- Operation operation = mOperations[index];
- if (operation != null) {
- // Note: SimpleDateFormat is not thread-safe, cannot be compile-time created,
- // and is relatively expensive to create during preloading. This method is only
- // used when dumping a connection, which is a rare (mainly error) case.
- SimpleDateFormat opDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
- int n = 0;
- do {
- StringBuilder msg = new StringBuilder();
- msg.append(" ").append(n).append(": [");
- String formattedStartTime = opDF.format(new Date(operation.mStartWallTime));
- msg.append(formattedStartTime);
- msg.append("] ");
- operation.describe(msg, false); // Never dump bingargs in a bugreport
- printer.println(msg.toString());
-
- if (index > 0) {
- index -= 1;
- } else {
- index = MAX_RECENT_OPERATIONS - 1;
- }
- n += 1;
- operation = mOperations[index];
- } while (operation != null && n < MAX_RECENT_OPERATIONS);
- } else {
+ if (index == 0) {
printer.println(" <none>");
+ return;
}
+
+ // Operations are dumped in order of most recent first.
+ int counter = 0;
+ int n = 0;
+ Operation operation = mOperations[index];
+ do {
+ printer.println(operation.describe(counter));
+
+ if (index > 0) {
+ index -= 1;
+ } else {
+ index = MAX_RECENT_OPERATIONS - 1;
+ }
+ n++;
+ counter++;
+ operation = mOperations[index];
+ } while (operation != null && n < MAX_RECENT_OPERATIONS);
+ counter += dumpIfNotRecentLocked(printer, mTransaction, counter);
+ }
+ }
+
+ private void dumpLongLocked(Printer printer) {
+ printer.println(" Operations exceeding " + LONG_OPERATION_THRESHOLD_MS + "ms:");
+ if (mLongOperations.isEmpty()) {
+ printer.println(" <none>");
+ return;
+ }
+ Operation[] longOps = mLongOperations.toArray();
+ for (int i = 0; i < longOps.length; i++) {
+ if (longOps[i] != null) {
+ printer.println(longOps[i].describe(i));
+ }
+ }
+ }
+
+ public long getTotalLongOperations() {
+ return mTotalLongOperations;
+ }
+
+ public void dump(Printer printer) {
+ synchronized (mOperations) {
+ dumpRecentLocked(printer);
+ dumpLongLocked(printer);
}
}
}
- private static final class Operation {
+ private final class Operation {
// Trim all SQL statements to 256 characters inside the trace marker.
// This limit gives plenty of context while leaving space for other
// entries in the trace buffer (and ensures atrace doesn't truncate the
// marker for us, potentially losing metadata in the process).
private static final int MAX_TRACE_METHOD_NAME_LEN = 256;
+ // The reserved start time that indicates the Operation is empty.
+ private static final long EMPTY_OPERATION = -1;
+
+ // The formatter for the timestamp.
+ private static final DateTimeFormatter sDateTime =
+ DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS", Locale.US);
+
public long mStartWallTime; // in System.currentTimeMillis()
public long mStartTime; // in SystemClock.uptimeMillis();
public long mEndTime; // in SystemClock.uptimeMillis();
@@ -1801,16 +1896,58 @@
public boolean mFinished;
public Exception mException;
public int mCookie;
- public String mPath;
public long mResultLong; // MIN_VALUE means "value not set".
public String mResultString;
+ /** Reset the object to begin a new operation. */
+ void start() {
+ mStartWallTime = System.currentTimeMillis();
+ mStartTime = SystemClock.uptimeMillis();
+ mEndTime = Long.MIN_VALUE;
+ mKind = null;
+ mSql = null;
+ if (mBindArgs != null) mBindArgs.clear();
+ mFinished = false;
+ mException = null;
+ mCookie = -1;
+ mResultLong = Long.MIN_VALUE;
+ mResultString = null;
+ }
+
+ /**
+ * Initialize from the source object. This is meant to clone the object for use in a
+ * transaction operation. To that end, the local bind args are set to null.
+ */
+ void copyFrom(Operation r) {
+ mStartWallTime = r.mStartWallTime;
+ mStartTime = r.mStartTime;
+ mEndTime = r.mEndTime;
+ mKind = r.mKind;
+ mSql = r.mSql;
+ mBindArgs = null;
+ mFinished = r.mFinished;
+ mException = r.mException;
+ mCookie = r.mCookie;
+ mResultLong = r.mResultLong;
+ mResultString = r.mResultString;
+ }
+
+ /** Mark the operation empty. */
+ void setEmpty() {
+ mStartWallTime = EMPTY_OPERATION;
+ }
+
+ /** Return true if the operation is empty. */
+ boolean isEmpty() {
+ return mStartWallTime == EMPTY_OPERATION;
+ }
+
public void describe(StringBuilder msg, boolean allowDetailedLog) {
msg.append(mKind);
if (mFinished) {
msg.append(" took ").append(mEndTime - mStartTime).append("ms");
} else {
- msg.append(" started ").append(System.currentTimeMillis() - mStartWallTime)
+ msg.append(" started ").append(SystemClock.uptimeMillis() - mStartTime)
.append("ms ago");
}
msg.append(" - ").append(getStatus());
@@ -1839,7 +1976,7 @@
}
msg.append("]");
}
- msg.append(", path=").append(mPath);
+ msg.append(", path=").append(mPool.getPath());
if (mException != null) {
msg.append(", exception=\"").append(mException.getMessage()).append("\"");
}
@@ -1851,6 +1988,21 @@
}
}
+ /**
+ * Convert a wall-clock time in milliseconds to logcat format.
+ */
+ private String timeString(long millis) {
+ return sDateTime.withZone(ZoneId.systemDefault()).format(Instant.ofEpochMilli(millis));
+ }
+
+ public String describe(int n) {
+ final StringBuilder msg = new StringBuilder();
+ final String start = timeString(mStartWallTime);
+ msg.append(" ").append(n).append(": [").append(start).append("] ");
+ describe(msg, false); // Never dump bingargs in a bugreport
+ return msg.toString();
+ }
+
private String getStatus() {
if (!mFinished) {
return "running";
@@ -1864,7 +2016,6 @@
return methodName.substring(0, MAX_TRACE_METHOD_NAME_LEN);
return methodName;
}
-
}
/**
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index ad335b6..15d7d66 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -1175,7 +1175,7 @@
+ ", isLegacyCompatibilityWalEnabled=" + isCompatibilityWalEnabled
+ ", journalMode=" + TextUtils.emptyIfNull(mConfiguration.resolveJournalMode())
+ ", syncMode=" + TextUtils.emptyIfNull(mConfiguration.resolveSyncMode()));
- printer.println(" IsReadOnlyDatabase=" + mConfiguration.isReadOnlyDatabase());
+ printer.println(" IsReadOnlyDatabase: " + mConfiguration.isReadOnlyDatabase());
if (isCompatibilityWalEnabled) {
printer.println(" Compatibility WAL enabled: wal_syncmode="
diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java
index 7d9f02d..3b14d9d 100644
--- a/core/java/android/database/sqlite/SQLiteSession.java
+++ b/core/java/android/database/sqlite/SQLiteSession.java
@@ -312,6 +312,15 @@
cancellationSignal);
}
+ private String modeString(int transactionMode) {
+ switch (transactionMode) {
+ case TRANSACTION_MODE_IMMEDIATE: return "TRANSACTION-IMMEDIATE";
+ case TRANSACTION_MODE_EXCLUSIVE: return "TRANSACTION-EXCLUSIVE";
+ case TRANSACTION_MODE_DEFERRED: return "TRANSACTION-DEFERRED";
+ default: return "TRANSACTION";
+ }
+ }
+
private void beginTransactionUnchecked(int transactionMode,
SQLiteTransactionListener transactionListener, int connectionFlags,
CancellationSignal cancellationSignal) {
@@ -321,6 +330,7 @@
if (mTransactionStack == null) {
acquireConnection(null, connectionFlags, cancellationSignal); // might throw
+ mConnection.recordBeginTransaction(modeString(transactionMode));
}
try {
// Set up the transaction such that we can back out safely
@@ -465,6 +475,7 @@
mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw
}
} finally {
+ mConnection.recordEndTransaction(successful);
releaseConnection(); // might throw
}
}
diff --git a/core/java/android/hardware/OverlayProperties.java b/core/java/android/hardware/OverlayProperties.java
index 88089ee..4a4d451 100644
--- a/core/java/android/hardware/OverlayProperties.java
+++ b/core/java/android/hardware/OverlayProperties.java
@@ -70,19 +70,6 @@
}
/**
- * @return True if the device can support fp16, false otherwise.
- * TODO: Move this to isCombinationSupported once the flag flips
- * @hide
- */
- public boolean isFp16SupportedForHdr() {
- if (mNativeObject == 0) {
- return false;
- }
- return nIsCombinationSupported(
- mNativeObject, DataSpace.DATASPACE_SCRGB, HardwareBuffer.RGBA_FP16);
- }
-
- /**
* Indicates that hardware composition of a buffer encoded with the provided {@link DataSpace}
* and {@link HardwareBuffer.Format} is supported on the device.
*
diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig
index 3ba8be4..ff07498 100644
--- a/core/java/android/hardware/biometrics/flags.aconfig
+++ b/core/java/android/hardware/biometrics/flags.aconfig
@@ -2,6 +2,7 @@
flag {
name: "last_authentication_time"
+ is_exported: true
namespace: "wallet_integration"
description: "Feature flag for adding getLastAuthenticationTime API to BiometricManager"
bug: "301979982"
@@ -9,6 +10,7 @@
flag {
name: "add_key_agreement_crypto_object"
+ is_exported: true
namespace: "biometrics"
description: "Feature flag for adding KeyAgreement api to CryptoObject."
bug: "282058146"
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index dc8f4b4..238c381 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -6098,7 +6098,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<android.util.Range<Float>> EFV_PADDING_ZOOM_FACTOR_RANGE =
new Key<android.util.Range<Float>>("android.efv.paddingZoomFactorRange", new TypeReference<android.util.Range<Float>>() {{ }});
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 749f218..6962811 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -142,7 +142,7 @@
/**
* An extension that aims to lock and stabilize a given region or object of interest.
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final int EXTENSION_EYES_FREE_VIDEOGRAPHY = 5;
/**
@@ -180,6 +180,16 @@
EXTENSION_HDR,
EXTENSION_NIGHT};
+ /**
+ * List of synthetic CameraCharacteristics keys that are supported in the extensions.
+ */
+ private static final List<CameraCharacteristics.Key>
+ SUPPORTED_SYNTHETIC_CAMERA_CHARACTERISTICS =
+ Arrays.asList(
+ CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES,
+ CameraCharacteristics.REQUEST_AVAILABLE_COLOR_SPACE_PROFILES
+ );
+
private final Context mContext;
private final String mCameraId;
private final Map<String, CameraCharacteristics> mCharacteristicsMap;
@@ -217,14 +227,18 @@
private static List<Size> generateSupportedSizes(List<SizeList> sizesList,
Integer format,
StreamConfigurationMap streamMap) {
- // Per API contract it is assumed that the extension is able to support all
- // camera advertised sizes for a given format in case it doesn't return
- // a valid non-empty size list.
ArrayList<Size> ret = getSupportedSizes(sizesList, format);
- Size[] supportedSizes = streamMap.getOutputSizes(format);
- if ((ret.isEmpty()) && (supportedSizes != null)) {
- ret.addAll(Arrays.asList(supportedSizes));
+
+ if (format == ImageFormat.JPEG || format == ImageFormat.YUV_420_888) {
+ // Per API contract it is assumed that the extension is able to support all
+ // camera advertised sizes for JPEG and YUV_420_888 in case it doesn't return
+ // a valid non-empty size list.
+ Size[] supportedSizes = streamMap.getOutputSizes(format);
+ if ((ret.isEmpty()) && (supportedSizes != null)) {
+ ret.addAll(Arrays.asList(supportedSizes));
+ }
}
+
return ret;
}
@@ -549,7 +563,7 @@
public ExtensionConnectionManager() {
IntArray extensionList = new IntArray(EXTENSION_LIST.length);
extensionList.addAll(EXTENSION_LIST);
- if (Flags.concertMode()) {
+ if (Flags.concertModeApi()) {
extensionList.add(EXTENSION_EYES_FREE_VIDEOGRAPHY);
}
@@ -752,7 +766,7 @@
IntArray extensionList = new IntArray(EXTENSION_LIST.length);
extensionList.addAll(EXTENSION_LIST);
- if (Flags.concertMode()) {
+ if (Flags.concertModeApi()) {
extensionList.add(EXTENSION_EYES_FREE_VIDEOGRAPHY);
}
@@ -874,11 +888,17 @@
Class<CameraCharacteristics.Key<?>> keyTyped =
(Class<CameraCharacteristics.Key<?>>) key;
- // Do not include synthetic keys. Including synthetic keys leads to undefined
- // behavior. This causes inclusion of capabilities that may not be supported in
- // camera extensions.
ret.addAll(chars.getAvailableKeyList(CameraCharacteristics.class, keyTyped, keys,
/*includeSynthetic*/ false));
+
+ // Add synthetic keys to the available key list if they are part of the supported
+ // synthetic camera characteristic key list
+ for (CameraCharacteristics.Key charKey :
+ SUPPORTED_SYNTHETIC_CAMERA_CHARACTERISTICS) {
+ if (chars.get(charKey) != null) {
+ ret.add(charKey);
+ }
+ }
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to query the extension for all available keys! Extension "
@@ -990,6 +1010,7 @@
case ImageFormat.YUV_420_888:
case ImageFormat.JPEG:
case ImageFormat.JPEG_R:
+ case ImageFormat.YCBCR_P010:
break;
default:
throw new IllegalArgumentException("Unsupported format: " + format);
@@ -1021,8 +1042,9 @@
return generateJpegSupportedSizes(
extenders.second.getSupportedPostviewResolutions(sz),
streamMap);
- } else if (format == ImageFormat.JPEG_R) {
- // Jpeg_R/UltraHDR is currently not supported in the basic extension case
+ } else if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) {
+ // Jpeg_R/UltraHDR + YCBCR_P010 is currently not supported in the basic
+ // extension case
return new ArrayList<>();
} else {
throw new IllegalArgumentException("Unsupported format: " + format);
@@ -1118,16 +1140,16 @@
*
* <p>Device-specific extensions currently support at most three
* multi-frame capture surface formats. ImageFormat.JPEG will be supported by all
- * extensions while ImageFormat.YUV_420_888 and ImageFormat.JPEG_R may or may not be
- * supported.</p>
+ * extensions while ImageFormat.YUV_420_888, ImageFormat.JPEG_R, or ImageFormat.YCBCR_P010
+ * may or may not be supported.</p>
*
* @param extension the extension type
* @param format device-specific extension output format
* @return non-modifiable list of available sizes or an empty list if the format is not
* supported.
* @throws IllegalArgumentException in case of format different from ImageFormat.JPEG,
- * ImageFormat.YUV_420_888, ImageFormat.JPEG_R; or
- * unsupported extension.
+ * ImageFormat.YUV_420_888, ImageFormat.JPEG_R,
+ * ImageFormat.YCBCR_P010; or unsupported extension.
*/
public @NonNull
List<Size> getExtensionSupportedSizes(@Extension int extension, int format) {
@@ -1151,6 +1173,7 @@
case ImageFormat.YUV_420_888:
case ImageFormat.JPEG:
case ImageFormat.JPEG_R:
+ case ImageFormat.YCBCR_P010:
break;
default:
throw new IllegalArgumentException("Unsupported format: " + format);
@@ -1183,8 +1206,9 @@
} else {
return generateSupportedSizes(null, format, streamMap);
}
- } else if (format == ImageFormat.JPEG_R) {
- // Jpeg_R/UltraHDR is currently not supported in the basic extension case
+ } else if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) {
+ // Jpeg_R/UltraHDR + YCBCR_P010 is currently not supported in the
+ // basic extension case
return new ArrayList<>();
} else {
throw new IllegalArgumentException("Unsupported format: " + format);
@@ -1213,7 +1237,8 @@
* @return the range of estimated minimal and maximal capture latency in milliseconds
* or null if no capture latency info can be provided
* @throws IllegalArgumentException in case of format different from {@link ImageFormat#JPEG},
- * {@link ImageFormat#YUV_420_888}, {@link ImageFormat#JPEG_R};
+ * {@link ImageFormat#YUV_420_888}, {@link ImageFormat#JPEG_R}
+ * {@link ImageFormat#YCBCR_P010};
* or unsupported extension.
*/
public @Nullable Range<Long> getEstimatedCaptureLatencyRangeMillis(@Extension int extension,
@@ -1222,6 +1247,7 @@
case ImageFormat.YUV_420_888:
case ImageFormat.JPEG:
case ImageFormat.JPEG_R:
+ case ImageFormat.YCBCR_P010:
//No op
break;
default:
@@ -1269,8 +1295,8 @@
// specific and cannot be estimated accurately enough.
return null;
}
- if (format == ImageFormat.JPEG_R) {
- // JpegR/UltraHDR is not supported for basic extensions
+ if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) {
+ // JpegR/UltraHDR + YCBCR_P010 is not supported for basic extensions
return null;
}
@@ -1522,7 +1548,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<android.util.Range<Float>> EFV_PADDING_ZOOM_FACTOR_RANGE =
CameraCharacteristics.EFV_PADDING_ZOOM_FACTOR_RANGE;
}
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 9fb561b..7754e32 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -3905,7 +3905,7 @@
* @see CaptureRequest#EFV_STABILIZATION_MODE
* @hide
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final int EFV_STABILIZATION_MODE_OFF = 0;
/**
@@ -3913,7 +3913,7 @@
* @see CaptureRequest#EFV_STABILIZATION_MODE
* @hide
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final int EFV_STABILIZATION_MODE_GIMBAL = 1;
/**
@@ -3923,7 +3923,7 @@
* @see CaptureRequest#EFV_STABILIZATION_MODE
* @hide
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final int EFV_STABILIZATION_MODE_LOCKED = 2;
//
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 66efccd1..13d5c7e 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -309,6 +309,8 @@
private Object mUserTag;
+ private boolean mReleaseSurfaces = false;
+
/**
* Construct empty request.
*
@@ -610,6 +612,9 @@
Parcelable[] parcelableArray = in.readParcelableArray(Surface.class.getClassLoader(),
Surface.class);
if (parcelableArray != null) {
+ if (Flags.surfaceLeakFix()) {
+ mReleaseSurfaces = true;
+ }
for (Parcelable p : parcelableArray) {
Surface s = (Surface) p;
mSurfaceSet.add(s);
@@ -792,6 +797,17 @@
}
}
+ @SuppressWarnings("Finalize")
+ @FlaggedApi(Flags.FLAG_SURFACE_LEAK_FIX)
+ @Override
+ protected void finalize() {
+ if (mReleaseSurfaces) {
+ for (Surface s : mSurfaceSet) {
+ s.release();
+ }
+ }
+ }
+
/**
* A builder for capture requests.
*
@@ -4319,7 +4335,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_PADDING_ZOOM_FACTOR =
new Key<Float>("android.efv.paddingZoomFactor", float.class);
@@ -4342,7 +4358,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Boolean> EFV_AUTO_ZOOM =
new Key<Boolean>("android.efv.autoZoom", boolean.class);
@@ -4363,7 +4379,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR =
new Key<Float>("android.efv.maxPaddingZoomFactor", float.class);
@@ -4390,7 +4406,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Integer> EFV_STABILIZATION_MODE =
new Key<Integer>("android.efv.stabilizationMode", int.class);
@@ -4412,7 +4428,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT =
new Key<android.util.Pair<Integer,Integer>>("android.efv.translateViewport", new TypeReference<android.util.Pair<Integer,Integer>>() {{ }});
@@ -4429,7 +4445,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_ROTATE_VIEWPORT =
new Key<Float>("android.efv.rotateViewport", float.class);
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index a01c23d..7145501 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -5940,7 +5940,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<int[]> EFV_PADDING_REGION =
new Key<int[]>("android.efv.paddingRegion", int[].class);
@@ -5961,7 +5961,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION =
new Key<int[]>("android.efv.autoZoomPaddingRegion", int[].class);
@@ -5984,7 +5984,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES =
new Key<android.graphics.PointF[]>("android.efv.targetCoordinates", android.graphics.PointF[].class);
@@ -6014,7 +6014,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_PADDING_ZOOM_FACTOR =
new Key<Float>("android.efv.paddingZoomFactor", float.class);
@@ -6041,7 +6041,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Integer> EFV_STABILIZATION_MODE =
new Key<Integer>("android.efv.stabilizationMode", int.class);
@@ -6064,7 +6064,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Boolean> EFV_AUTO_ZOOM =
new Key<Boolean>("android.efv.autoZoom", boolean.class);
@@ -6081,7 +6081,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_ROTATE_VIEWPORT =
new Key<Float>("android.efv.rotateViewport", float.class);
@@ -6103,7 +6103,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT =
new Key<android.util.Pair<Integer,Integer>>("android.efv.translateViewport", new TypeReference<android.util.Pair<Integer,Integer>>() {{ }});
@@ -6124,7 +6124,7 @@
* @hide
*/
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR =
new Key<Float>("android.efv.maxPaddingZoomFactor", float.class);
diff --git a/core/java/android/hardware/camera2/ExtensionCaptureRequest.java b/core/java/android/hardware/camera2/ExtensionCaptureRequest.java
index 32039c6..c33956b 100644
--- a/core/java/android/hardware/camera2/ExtensionCaptureRequest.java
+++ b/core/java/android/hardware/camera2/ExtensionCaptureRequest.java
@@ -40,7 +40,7 @@
* @see CaptureRequest
* @see CameraExtensionSession
*/
-@FlaggedApi(Flags.FLAG_CONCERT_MODE)
+@FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public final class ExtensionCaptureRequest {
/**
@@ -74,7 +74,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_PADDING_ZOOM_FACTOR = CaptureRequest.EFV_PADDING_ZOOM_FACTOR;
/**
@@ -99,7 +99,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Boolean> EFV_AUTO_ZOOM = CaptureRequest.EFV_AUTO_ZOOM;
/**
@@ -125,7 +125,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR = CaptureRequest.EFV_MAX_PADDING_ZOOM_FACTOR;
/**
@@ -152,7 +152,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Integer> EFV_STABILIZATION_MODE = CaptureRequest.EFV_STABILIZATION_MODE;
/**
@@ -176,7 +176,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT = CaptureRequest.EFV_TRANSLATE_VIEWPORT;
/**
@@ -193,7 +193,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_ROTATE_VIEWPORT = CaptureRequest.EFV_ROTATE_VIEWPORT;
@@ -205,14 +205,14 @@
* <p>No stabilization.</p>
* @see ExtensionCaptureRequest#EFV_STABILIZATION_MODE
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final int EFV_STABILIZATION_MODE_OFF = CaptureRequest.EFV_STABILIZATION_MODE_OFF;
/**
* <p>Gimbal stabilization mode.</p>
* @see ExtensionCaptureRequest#EFV_STABILIZATION_MODE
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final int EFV_STABILIZATION_MODE_GIMBAL = CaptureRequest.EFV_STABILIZATION_MODE_GIMBAL;
/**
@@ -221,7 +221,7 @@
* stabilization to directionally steady the target region.</p>
* @see ExtensionCaptureRequest#EFV_STABILIZATION_MODE
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final int EFV_STABILIZATION_MODE_LOCKED = CaptureRequest.EFV_STABILIZATION_MODE_LOCKED;
-}
\ No newline at end of file
+}
diff --git a/core/java/android/hardware/camera2/ExtensionCaptureResult.java b/core/java/android/hardware/camera2/ExtensionCaptureResult.java
index 5c99909..95feb2f 100644
--- a/core/java/android/hardware/camera2/ExtensionCaptureResult.java
+++ b/core/java/android/hardware/camera2/ExtensionCaptureResult.java
@@ -42,7 +42,7 @@
* @see CaptureRequest
* @see CameraExtensionSession
*/
-@FlaggedApi(Flags.FLAG_CONCERT_MODE)
+@FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public final class ExtensionCaptureResult {
/**
@@ -66,7 +66,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<int[]> EFV_PADDING_REGION = CaptureResult.EFV_PADDING_REGION;
/**
@@ -90,7 +90,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION = CaptureResult.EFV_AUTO_ZOOM_PADDING_REGION;
/**
@@ -113,7 +113,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES = CaptureResult.EFV_TARGET_COORDINATES;
/**
@@ -147,7 +147,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_PADDING_ZOOM_FACTOR = CaptureResult.EFV_PADDING_ZOOM_FACTOR;
/**
@@ -174,7 +174,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Integer> EFV_STABILIZATION_MODE = CaptureResult.EFV_STABILIZATION_MODE;
/**
@@ -199,7 +199,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Boolean> EFV_AUTO_ZOOM = CaptureResult.EFV_AUTO_ZOOM;
/**
@@ -216,7 +216,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_ROTATE_VIEWPORT = CaptureResult.EFV_ROTATE_VIEWPORT;
/**
@@ -240,7 +240,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT = CaptureResult.EFV_TRANSLATE_VIEWPORT;
/**
@@ -266,7 +266,7 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR = CaptureResult.EFV_MAX_PADDING_ZOOM_FACTOR;
-}
\ No newline at end of file
+}
diff --git a/core/java/android/hardware/camera2/extension/AdvancedExtender.java b/core/java/android/hardware/camera2/extension/AdvancedExtender.java
index 4895f38..8fa09a8 100644
--- a/core/java/android/hardware/camera2/extension/AdvancedExtender.java
+++ b/core/java/android/hardware/camera2/extension/AdvancedExtender.java
@@ -61,7 +61,6 @@
private CameraUsageTracker mCameraUsageTracker;
private static final String TAG = "AdvancedExtender";
-
/**
* Initialize a camera extension advanced extender instance.
*
@@ -263,6 +262,13 @@
*
* <p>For example, an extension may limit the zoom ratio range. In this case, an OEM can return
* a new zoom ratio range for the key {@link CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE}.
+ *
+ * <p> Currently, the only synthetic keys supported for override are
+ * {@link CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES} and
+ * {@link CameraCharacteristics#REQUEST_AVAILABLE_COLOR_SPACE_PROFILES}. To enable them, an OEM
+ * should override the respective native keys
+ * {@link CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP} and
+ * {@link CameraCharacteristics#REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP}.
*/
@FlaggedApi(Flags.FLAG_CAMERA_EXTENSIONS_CHARACTERISTICS_GET)
@NonNull
diff --git a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
index 509bcb8..5567bed 100644
--- a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
+++ b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
@@ -27,6 +27,7 @@
int imageFormat;
int capacity;
long usage;
+ long dynamicRangeProfile;
const int TYPE_SURFACE = 0;
const int TYPE_IMAGEREADER = 1;
diff --git a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
index 53f56bc..001b794 100644
--- a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
+++ b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
@@ -133,15 +133,4 @@
@DynamicRangeProfiles.Profile long dynamicRangeProfile) {
mOutputSurface.dynamicRangeProfile = dynamicRangeProfile;
}
-
- /**
- * Set the color space. The default colorSpace
- * will be
- * {@link android.hardware.camera2.params.ColorSpaceProfiles.UNSPECIFIED}
- * unless explicitly set using this method.
- */
- @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT)
- public void setColorSpace(int colorSpace) {
- mOutputSurface.colorSpace = colorSpace;
- }
}
diff --git a/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
index 84ca2b6..9d46b55 100644
--- a/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
+++ b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
@@ -25,4 +25,5 @@
CameraMetadataNative sessionParameter;
int sessionTemplateId;
int sessionType;
+ int colorSpace = -1;
}
diff --git a/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
index 96c88e6..84b7a7f 100644
--- a/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
+++ b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
@@ -22,6 +22,7 @@
import android.annotation.SystemApi;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.ColorSpaceProfiles;
import android.os.IBinder;
import com.android.internal.camera.flags.Flags;
@@ -48,6 +49,7 @@
private final int mSessionTemplateId;
private final List<ExtensionOutputConfiguration> mOutputs;
private final CaptureRequest mSessionParameters;
+ private int mColorSpace;
/**
* Initialize an extension configuration instance
@@ -72,6 +74,18 @@
mSessionTemplateId = sessionTemplateId;
mOutputs = outputs;
mSessionParameters = sessionParams;
+ mColorSpace = ColorSpaceProfiles.UNSPECIFIED;
+ }
+
+ /**
+ * Set the color space using the ordinal value of a
+ * {@link android.graphics.ColorSpace.Named}.
+ * The default will be -1, indicating an unspecified ColorSpace,
+ * unless explicitly set using this method.
+ */
+ @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT)
+ public void setColorSpace(int colorSpace) {
+ mColorSpace = colorSpace;
}
@FlaggedApi(Flags.FLAG_CONCERT_MODE)
@@ -84,6 +98,11 @@
ret.sessionTemplateId = mSessionTemplateId;
ret.sessionType = mSessionType;
ret.outputConfigs = new ArrayList<>(mOutputs.size());
+ if (Flags.extension10Bit()) {
+ ret.colorSpace = mColorSpace;
+ } else {
+ ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
+ }
for (ExtensionOutputConfiguration outputConfig : mOutputs) {
ret.outputConfigs.add(outputConfig.getOutputConfig());
}
diff --git a/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
index 9dc6d7b..3a67d61 100644
--- a/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
+++ b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.hardware.camera2.params.DynamicRangeProfiles;
import com.android.internal.camera.flags.Flags;
@@ -79,6 +80,11 @@
config.outputId = new OutputConfigId();
config.outputId.id = mOutputConfigId;
config.surfaceGroupId = mSurfaceGroupId;
+ if (Flags.extension10Bit()) {
+ config.dynamicRangeProfile = surface.getDynamicRangeProfile();
+ } else {
+ config.dynamicRangeProfile = DynamicRangeProfiles.STANDARD;
+ }
}
@Nullable CameraOutputConfig getOutputConfig() {
diff --git a/core/java/android/hardware/camera2/extension/SessionProcessor.java b/core/java/android/hardware/camera2/extension/SessionProcessor.java
index 2e428e5..0ec5c0a 100644
--- a/core/java/android/hardware/camera2/extension/SessionProcessor.java
+++ b/core/java/android/hardware/camera2/extension/SessionProcessor.java
@@ -363,11 +363,20 @@
private final class SessionProcessorImpl extends ISessionProcessorImpl.Stub {
private long mVendorId = -1;
+ OutputSurface mImageCaptureSurface;
+ OutputSurface mPreviewSurface;
+ OutputSurface mPostviewSurface;
+
@Override
public CameraSessionConfig initSession(IBinder token, String cameraId,
Map<String, CameraMetadataNative> charsMap, OutputSurface previewSurface,
OutputSurface imageCaptureSurface, OutputSurface postviewSurface)
throws RemoteException {
+ if (Flags.surfaceLeakFix()) {
+ mPreviewSurface = previewSurface;
+ mPostviewSurface = postviewSurface;
+ mImageCaptureSurface = imageCaptureSurface;
+ }
ExtensionConfiguration config = SessionProcessor.this.initSession(token, cameraId,
new CharacteristicsMap(charsMap),
new CameraOutputSurface(previewSurface),
@@ -390,6 +399,17 @@
@Override
public void deInitSession(IBinder token) throws RemoteException {
SessionProcessor.this.deInitSession(token);
+ if (Flags.surfaceLeakFix()) {
+ if ((mPreviewSurface != null) && (mPreviewSurface.surface != null)) {
+ mPreviewSurface.surface.release();
+ }
+ if ((mImageCaptureSurface != null) && (mImageCaptureSurface.surface != null)) {
+ mImageCaptureSurface.surface.release();
+ }
+ if ((mPostviewSurface != null) && (mPostviewSurface.surface != null)) {
+ mPostviewSurface.surface.release();
+ }
+ }
}
@Override
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index a7d6caf..6d9b51cb 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.content.Context;
+import android.graphics.ColorSpace;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.SyncFence;
@@ -49,6 +50,7 @@
import android.hardware.camera2.extension.ParcelImage;
import android.hardware.camera2.extension.ParcelTotalCaptureResult;
import android.hardware.camera2.extension.Request;
+import android.hardware.camera2.params.ColorSpaceProfiles;
import android.hardware.camera2.params.DynamicRangeProfiles;
import android.hardware.camera2.params.ExtensionSessionConfiguration;
import android.hardware.camera2.params.OutputConfiguration;
@@ -62,6 +64,7 @@
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
+import android.util.IntArray;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
@@ -97,6 +100,9 @@
private Surface mClientRepeatingRequestSurface;
private Surface mClientCaptureSurface;
private Surface mClientPostviewSurface;
+ private OutputConfiguration mClientRepeatingRequestOutputConfig;
+ private OutputConfiguration mClientCaptureOutputConfig;
+ private OutputConfiguration mClientPostviewOutputConfig;
private CameraCaptureSession mCaptureSession = null;
private ISessionProcessorImpl mSessionProcessor = null;
private final InitializeSessionHandler mInitializeHandler;
@@ -142,8 +148,19 @@
for (OutputConfiguration c : config.getOutputConfigurations()) {
if (c.getDynamicRangeProfile() != DynamicRangeProfiles.STANDARD) {
- throw new IllegalArgumentException("Unsupported dynamic range profile: " +
- c.getDynamicRangeProfile());
+ if (Flags.extension10Bit() && Flags.cameraExtensionsCharacteristicsGet()) {
+ DynamicRangeProfiles dynamicProfiles = extensionChars.get(
+ config.getExtension(),
+ CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES);
+ if (dynamicProfiles == null || !dynamicProfiles.getSupportedProfiles()
+ .contains(c.getDynamicRangeProfile())) {
+ throw new IllegalArgumentException("Unsupported dynamic range profile: "
+ + c.getDynamicRangeProfile());
+ }
+ } else {
+ throw new IllegalArgumentException("Unsupported dynamic range profile: "
+ + c.getDynamicRangeProfile());
+ }
}
if (c.getStreamUseCase() !=
CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT) {
@@ -157,12 +174,26 @@
config.getExtension(), SurfaceTexture.class);
Surface repeatingRequestSurface = CameraExtensionUtils.getRepeatingRequestSurface(
config.getOutputConfigurations(), supportedPreviewSizes);
+ OutputConfiguration repeatingRequestOutputConfig = null;
if (repeatingRequestSurface != null) {
+ for (OutputConfiguration outputConfig : config.getOutputConfigurations()) {
+ if (outputConfig.getSurface() == repeatingRequestSurface) {
+ repeatingRequestOutputConfig = outputConfig;
+ }
+ }
suitableSurfaceCount++;
}
HashMap<Integer, List<Size>> supportedCaptureSizes = new HashMap<>();
- for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+
+ IntArray supportedCaptureOutputFormats =
+ new IntArray(CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.length);
+ supportedCaptureOutputFormats.addAll(
+ CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS);
+ if (Flags.extension10Bit()) {
+ supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010);
+ }
+ for (int format : supportedCaptureOutputFormats.toArray()) {
List<Size> supportedSizes = extensionChars.getExtensionSupportedSizes(
config.getExtension(), format);
if (supportedSizes != null) {
@@ -171,7 +202,13 @@
}
Surface burstCaptureSurface = CameraExtensionUtils.getBurstCaptureSurface(
config.getOutputConfigurations(), supportedCaptureSizes);
+ OutputConfiguration burstCaptureOutputConfig = null;
if (burstCaptureSurface != null) {
+ for (OutputConfiguration outputConfig : config.getOutputConfigurations()) {
+ if (outputConfig.getSurface() == burstCaptureSurface) {
+ burstCaptureOutputConfig = outputConfig;
+ }
+ }
suitableSurfaceCount++;
}
@@ -180,13 +217,14 @@
}
Surface postviewSurface = null;
+ OutputConfiguration postviewOutputConfig = config.getPostviewOutputConfiguration();
if (burstCaptureSurface != null && config.getPostviewOutputConfiguration() != null) {
CameraExtensionUtils.SurfaceInfo burstCaptureSurfaceInfo =
CameraExtensionUtils.querySurface(burstCaptureSurface);
Size burstCaptureSurfaceSize =
new Size(burstCaptureSurfaceInfo.mWidth, burstCaptureSurfaceInfo.mHeight);
HashMap<Integer, List<Size>> supportedPostviewSizes = new HashMap<>();
- for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+ for (int format : supportedCaptureOutputFormats.toArray()) {
List<Size> supportedSizesPostview = extensionChars.getPostviewSupportedSizes(
config.getExtension(), burstCaptureSurfaceSize, format);
if (supportedSizesPostview != null) {
@@ -207,8 +245,8 @@
extender.init(cameraId, characteristicsMapNative);
CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(ctx,
- extender, cameraDevice, characteristicsMapNative, repeatingRequestSurface,
- burstCaptureSurface, postviewSurface, config.getStateCallback(),
+ extender, cameraDevice, characteristicsMapNative, repeatingRequestOutputConfig,
+ burstCaptureOutputConfig, postviewOutputConfig, config.getStateCallback(),
config.getExecutor(), sessionId, token, config.getExtension());
ret.mStatsAggregator.setClientName(ctx.getOpPackageName());
@@ -223,8 +261,9 @@
@NonNull IAdvancedExtenderImpl extender,
@NonNull CameraDeviceImpl cameraDevice,
Map<String, CameraMetadataNative> characteristicsMap,
- @Nullable Surface repeatingRequestSurface, @Nullable Surface burstCaptureSurface,
- @Nullable Surface postviewSurface,
+ @Nullable OutputConfiguration repeatingRequestOutputConfig,
+ @Nullable OutputConfiguration burstCaptureOutputConfig,
+ @Nullable OutputConfiguration postviewOutputConfig,
@NonNull StateCallback callback, @NonNull Executor executor,
int sessionId,
@NonNull IBinder token,
@@ -235,9 +274,18 @@
mCharacteristicsMap = characteristicsMap;
mCallbacks = callback;
mExecutor = executor;
- mClientRepeatingRequestSurface = repeatingRequestSurface;
- mClientCaptureSurface = burstCaptureSurface;
- mClientPostviewSurface = postviewSurface;
+ mClientRepeatingRequestOutputConfig = repeatingRequestOutputConfig;
+ mClientCaptureOutputConfig = burstCaptureOutputConfig;
+ mClientPostviewOutputConfig = postviewOutputConfig;
+ if (repeatingRequestOutputConfig != null) {
+ mClientRepeatingRequestSurface = repeatingRequestOutputConfig.getSurface();
+ }
+ if (burstCaptureOutputConfig != null) {
+ mClientCaptureSurface = burstCaptureOutputConfig.getSurface();
+ }
+ if (postviewOutputConfig != null) {
+ mClientPostviewSurface = postviewOutputConfig.getSurface();
+ }
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
@@ -262,9 +310,9 @@
return;
}
- OutputSurface previewSurface = initializeParcelable(mClientRepeatingRequestSurface);
- OutputSurface captureSurface = initializeParcelable(mClientCaptureSurface);
- OutputSurface postviewSurface = initializeParcelable(mClientPostviewSurface);
+ OutputSurface previewSurface = initializeParcelable(mClientRepeatingRequestOutputConfig);
+ OutputSurface captureSurface = initializeParcelable(mClientCaptureOutputConfig);
+ OutputSurface postviewSurface = initializeParcelable(mClientPostviewOutputConfig);
mSessionProcessor = mAdvancedExtender.getSessionProcessor();
CameraSessionConfig sessionConfig = mSessionProcessor.initSession(mToken,
@@ -300,6 +348,23 @@
cameraOutput.setTimestampBase(OutputConfiguration.TIMESTAMP_BASE_SENSOR);
cameraOutput.setReadoutTimestampEnabled(false);
cameraOutput.setPhysicalCameraId(output.physicalCameraId);
+ if (Flags.extension10Bit()) {
+ boolean validDynamicRangeProfile = false;
+ for (long profile = DynamicRangeProfiles.STANDARD;
+ profile < DynamicRangeProfiles.PUBLIC_MAX; profile <<= 1) {
+ if (output.dynamicRangeProfile == profile) {
+ validDynamicRangeProfile = true;
+ break;
+ }
+ }
+ if (validDynamicRangeProfile) {
+ cameraOutput.setDynamicRangeProfile(output.dynamicRangeProfile);
+ } else {
+ Log.e(TAG, "Extension configured dynamic range profile "
+ + output.dynamicRangeProfile
+ + " is not valid, using default DynamicRangeProfile.STANDARD");
+ }
+ }
outputList.add(cameraOutput);
mCameraConfigMap.put(cameraOutput.getSurface(), output);
}
@@ -314,7 +379,16 @@
SessionConfiguration sessionConfiguration = new SessionConfiguration(sessionType,
outputList, new CameraExtensionUtils.HandlerExecutor(mHandler),
new SessionStateHandler());
-
+ if (Flags.extension10Bit()) {
+ if (sessionConfig.colorSpace >= 0
+ && sessionConfig.colorSpace < ColorSpace.Named.values().length) {
+ sessionConfiguration.setColorSpace(
+ ColorSpace.Named.values()[sessionConfig.colorSpace]);
+ } else {
+ Log.e(TAG, "Extension configured color space " + sessionConfig.colorSpace
+ + " is not valid, using default unspecified color space");
+ }
+ }
if ((sessionConfig.sessionParameter != null) &&
(!sessionConfig.sessionParameter.isEmpty())) {
CaptureRequest.Builder requestBuilder = mCameraDevice.createCaptureRequest(
@@ -362,21 +436,38 @@
return ret;
}
- private static OutputSurface initializeParcelable(Surface s) {
+ private static OutputSurface initializeParcelable(OutputConfiguration o) {
OutputSurface ret = new OutputSurface();
- if (s != null) {
+
+ if (o != null && o.getSurface() != null) {
+ Surface s = o.getSurface();
ret.surface = s;
ret.size = new android.hardware.camera2.extension.Size();
Size surfaceSize = SurfaceUtils.getSurfaceSize(s);
ret.size.width = surfaceSize.getWidth();
ret.size.height = surfaceSize.getHeight();
ret.imageFormat = SurfaceUtils.getSurfaceFormat(s);
+
+ if (Flags.extension10Bit()) {
+ ret.dynamicRangeProfile = o.getDynamicRangeProfile();
+ ColorSpace colorSpace = o.getColorSpace();
+ if (colorSpace != null) {
+ ret.colorSpace = colorSpace.getId();
+ } else {
+ ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
+ }
+ } else {
+ ret.dynamicRangeProfile = DynamicRangeProfiles.STANDARD;
+ ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
+ }
} else {
ret.surface = null;
ret.size = new android.hardware.camera2.extension.Size();
ret.size.width = -1;
ret.size.height = -1;
ret.imageFormat = ImageFormat.UNKNOWN;
+ ret.dynamicRangeProfile = DynamicRangeProfiles.STANDARD;
+ ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
}
return ret;
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 725b413..5b32f33 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -58,12 +58,15 @@
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
+import android.util.IntArray;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.Pair;
import android.util.Size;
import android.view.Surface;
+import com.android.internal.camera.flags.Flags;
+
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
@@ -183,7 +186,14 @@
}
HashMap<Integer, List<Size>> supportedCaptureSizes = new HashMap<>();
- for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+ IntArray supportedCaptureOutputFormats =
+ new IntArray(CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.length);
+ supportedCaptureOutputFormats.addAll(
+ CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS);
+ if (Flags.extension10Bit()) {
+ supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010);
+ }
+ for (int format : supportedCaptureOutputFormats.toArray()) {
List<Size> supportedSizes = extensionChars.getExtensionSupportedSizes(
config.getExtension(), format);
if (supportedSizes != null) {
@@ -207,7 +217,7 @@
Size burstCaptureSurfaceSize =
new Size(burstCaptureSurfaceInfo.mWidth, burstCaptureSurfaceInfo.mHeight);
HashMap<Integer, List<Size>> supportedPostviewSizes = new HashMap<>();
- for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+ for (int format : supportedCaptureOutputFormats.toArray()) {
List<Size> supportedSizesPostview = extensionChars.getPostviewSupportedSizes(
config.getExtension(), burstCaptureSurfaceSize, format);
if (supportedSizesPostview != null) {
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
index a8066aa..f0c6e2e 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
@@ -29,10 +29,13 @@
import android.media.Image;
import android.media.ImageWriter;
import android.os.Handler;
+import android.util.IntArray;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
+import com.android.internal.camera.flags.Flags;
+
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -130,9 +133,16 @@
public static Surface getBurstCaptureSurface(
@NonNull List<OutputConfiguration> outputConfigs,
@NonNull HashMap<Integer, List<Size>> supportedCaptureSizes) {
+ IntArray supportedCaptureOutputFormats =
+ new IntArray(CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.length);
+ supportedCaptureOutputFormats.addAll(
+ CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS);
+ if (Flags.extension10Bit()) {
+ supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010);
+ }
for (OutputConfiguration config : outputConfigs) {
SurfaceInfo surfaceInfo = querySurface(config.getSurface());
- for (int supportedFormat : SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+ for (int supportedFormat : supportedCaptureOutputFormats.toArray()) {
if (surfaceInfo.mFormat == supportedFormat) {
Size captureSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight);
if (supportedCaptureSizes.containsKey(supportedFormat)) {
diff --git a/core/java/android/hardware/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java
index e35e801..76888f3 100644
--- a/core/java/android/hardware/devicestate/DeviceState.java
+++ b/core/java/android/hardware/devicestate/DeviceState.java
@@ -25,8 +25,9 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-
-import com.android.internal.util.Preconditions;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArraySet;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -37,8 +38,7 @@
import java.util.Set;
/**
- * A state of the device defined by the {@link DeviceStateProvider} and managed by the
- * {@link DeviceStateManagerService}.
+ * A state of the device managed by {@link DeviceStateManager}.
* <p>
* Device state is an abstract concept that allows mapping the current state of the device to the
* state of the system. This is useful for variable-state devices, like foldable or rollable
@@ -268,68 +268,88 @@
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public @interface DeviceStateProperties {}
- /** Unique identifier for the device state. */
- @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to = MAXIMUM_DEVICE_STATE_IDENTIFIER)
- private final int mIdentifier;
+ /** @hide */
+ @IntDef(prefix = {"PROPERTY_"}, flag = true, value = {
+ PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED,
+ PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN,
+ PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ public @interface PhysicalDeviceStateProperties {}
- /** String description of the device state. */
+ /** @hide */
+ @IntDef(prefix = {"PROPERTY_"}, flag = true, value = {
+ PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS,
+ PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP,
+ PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL,
+ PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE,
+ PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST,
+ PROPERTY_APP_INACCESSIBLE,
+ PROPERTY_EMULATED_ONLY,
+ PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY,
+ PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY,
+ PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP,
+ PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE,
+ PROPERTY_EXTENDED_DEVICE_STATE_EXTERNAL_DISPLAY,
+ PROPERTY_FEATURE_REAR_DISPLAY,
+ PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ public @interface SystemDeviceStateProperties {}
+
@NonNull
- private final String mName;
+ private final DeviceState.Configuration mDeviceStateConfiguration;
@DeviceStateFlags
private final int mFlags;
- private final Set<@DeviceStateProperties Integer> mProperties;
+ /** @hide */
+ public DeviceState(@NonNull DeviceState.Configuration deviceStateConfiguration) {
+ Objects.requireNonNull(deviceStateConfiguration, "Device StateConfiguration is null");
+ mDeviceStateConfiguration = deviceStateConfiguration;
+ mFlags = 0;
+ }
+
+ /** @hide */
+ public DeviceState(
+ @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to =
+ MAXIMUM_DEVICE_STATE_IDENTIFIER) int identifier,
+ @NonNull String name,
+ @NonNull Set<@DeviceStateProperties Integer> properties) {
+ mDeviceStateConfiguration = new DeviceState.Configuration(identifier, name, properties,
+ Collections.emptySet());
+ mFlags = 0;
+ }
/**
* @deprecated Deprecated in favor of {@link #DeviceState(int, String, Set)}
* @hide
*/
// TODO(b/325124054): Make non-default and remove deprecated callback methods.
- @TestApi
@Deprecated
public DeviceState(
@IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to =
MAXIMUM_DEVICE_STATE_IDENTIFIER) int identifier,
@NonNull String name,
@DeviceStateFlags int flags) {
- Preconditions.checkArgumentInRange(identifier, MINIMUM_DEVICE_STATE_IDENTIFIER,
- MAXIMUM_DEVICE_STATE_IDENTIFIER,
- "identifier");
- mIdentifier = identifier;
- mName = name;
+ mDeviceStateConfiguration = new DeviceState.Configuration(identifier, name,
+ Collections.emptySet(), Collections.emptySet());
mFlags = flags;
- mProperties = Collections.emptySet();
- }
-
- /** @hide */
- @TestApi
- public DeviceState(
- @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to =
- MAXIMUM_DEVICE_STATE_IDENTIFIER) int identifier,
- @NonNull String name,
- @NonNull Set<@DeviceStateProperties Integer> properties) {
- Preconditions.checkArgumentInRange(identifier, MINIMUM_DEVICE_STATE_IDENTIFIER,
- MAXIMUM_DEVICE_STATE_IDENTIFIER,
- "identifier");
-
- mIdentifier = identifier;
- mName = name;
- mProperties = Set.copyOf(properties);
- mFlags = 0;
}
/** Returns the unique identifier for the device state. */
@IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER)
public int getIdentifier() {
- return mIdentifier;
+ return mDeviceStateConfiguration.getIdentifier();
}
/** Returns a string description of the device state. */
@NonNull
public String getName() {
- return mName;
+ return mDeviceStateConfiguration.getName();
}
/**
@@ -345,10 +365,13 @@
@Override
public String toString() {
- return "DeviceState{" + "identifier=" + mIdentifier + ", name='" + mName + '\''
- + ", app_accessible=" + !hasProperty(PROPERTY_APP_INACCESSIBLE)
+ return "DeviceState{" + "identifier=" + mDeviceStateConfiguration.getIdentifier()
+ + ", name='" + mDeviceStateConfiguration.getName() + '\''
+ + ", app_accessible=" + !mDeviceStateConfiguration.getSystemProperties().contains(
+ PROPERTY_APP_INACCESSIBLE)
+ ", cancel_when_requester_not_on_top="
- + hasProperty(PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP)
+ + mDeviceStateConfiguration.getSystemProperties().contains(
+ PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP)
+ "}";
}
@@ -357,14 +380,12 @@
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DeviceState that = (DeviceState) o;
- return mIdentifier == that.mIdentifier
- && Objects.equals(mName, that.mName)
- && Objects.equals(mProperties, that.mProperties);
+ return Objects.equals(mDeviceStateConfiguration, that.mDeviceStateConfiguration);
}
@Override
public int hashCode() {
- return Objects.hash(mIdentifier, mName, mProperties);
+ return Objects.hash(mDeviceStateConfiguration);
}
/** Checks if a specific flag is set
@@ -381,7 +402,8 @@
* Checks if a specific property is set on this state
*/
public boolean hasProperty(@DeviceStateProperties int propertyToCheckFor) {
- return mProperties.contains(propertyToCheckFor);
+ return mDeviceStateConfiguration.mSystemProperties.contains(propertyToCheckFor)
+ || mDeviceStateConfiguration.mPhysicalProperties.contains(propertyToCheckFor);
}
/**
@@ -389,10 +411,193 @@
*/
public boolean hasProperties(@NonNull @DeviceStateProperties int... properties) {
for (int i = 0; i < properties.length; i++) {
- if (mProperties.contains(properties[i])) {
+ if (!hasProperty(properties[i])) {
return false;
}
}
return true;
}
+
+ /**
+ * Returns the underlying {@link DeviceState.Configuration} object used to model the
+ * device state.
+ * @hide
+ */
+ public Configuration getConfiguration() {
+ return mDeviceStateConfiguration;
+ }
+
+ /**
+ * Detailed description of a {@link DeviceState} that includes separated sets of
+ * {@link DeviceStateProperties} for properties that correspond to the state of the system when
+ * the device is in this state, as well as physical properties that describe this state.
+ *
+ * Instantiation of this class should only be done by the system server, and clients of
+ * {@link DeviceStateManager} will receive {@link DeviceState} objects.
+ *
+ * @see DeviceStateManager
+ * @hide
+ */
+ public static final class Configuration implements Parcelable {
+ /** Unique identifier for the device state. */
+ @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to = MAXIMUM_DEVICE_STATE_IDENTIFIER)
+ private final int mIdentifier;
+
+ /** String description of the device state. */
+ @NonNull
+ private final String mName;
+
+ /** {@link ArraySet} of system properties that apply to this state. */
+ @NonNull
+ private final ArraySet<@SystemDeviceStateProperties Integer> mSystemProperties;
+
+ /** {@link ArraySet} of physical device properties that apply to this state. */
+ @NonNull
+ private final ArraySet<@PhysicalDeviceStateProperties Integer> mPhysicalProperties;
+
+ private Configuration(int identifier, @NonNull String name,
+ @NonNull Set<@SystemDeviceStateProperties Integer> systemProperties,
+ @NonNull Set<@PhysicalDeviceStateProperties Integer> physicalProperties) {
+ mIdentifier = identifier;
+ mName = name;
+ mSystemProperties = new ArraySet<@SystemDeviceStateProperties Integer>(
+ systemProperties);
+ mPhysicalProperties = new ArraySet<@PhysicalDeviceStateProperties Integer>(
+ physicalProperties);
+ }
+
+ /** Returns the unique identifier for the device state. */
+ public int getIdentifier() {
+ return mIdentifier;
+ }
+
+ /** Returns a string description of the device state. */
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ /** Returns the {@link Set} of system properties that apply to this state. */
+ @NonNull
+ public Set<@SystemDeviceStateProperties Integer> getSystemProperties() {
+ return mSystemProperties;
+ }
+
+ /** Returns the {@link Set} of physical device properties that apply to this state. */
+ @NonNull
+ public Set<@DeviceStateProperties Integer> getPhysicalProperties() {
+ return mPhysicalProperties;
+ }
+
+ @Override
+ public String toString() {
+ return "DeviceState{" + "identifier=" + mIdentifier
+ + ", name='" + mName + '\''
+ + ", app_accessible=" + mSystemProperties.contains(PROPERTY_APP_INACCESSIBLE)
+ + ", cancel_when_requester_not_on_top="
+ + mSystemProperties.contains(PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP)
+ + "}";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ DeviceState.Configuration that = (DeviceState.Configuration) o;
+ return mIdentifier == that.mIdentifier
+ && Objects.equals(mName, that.mName)
+ && Objects.equals(mSystemProperties, that.mSystemProperties)
+ && Objects.equals(mPhysicalProperties, that.mPhysicalProperties);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mIdentifier, mName, mSystemProperties, mPhysicalProperties);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mIdentifier);
+ dest.writeString8(mName);
+
+ dest.writeInt(mSystemProperties.size());
+ for (int i = 0; i < mSystemProperties.size(); i++) {
+ dest.writeInt(mSystemProperties.valueAt(i));
+ }
+
+ dest.writeInt(mPhysicalProperties.size());
+ for (int i = 0; i < mPhysicalProperties.size(); i++) {
+ dest.writeInt(mPhysicalProperties.valueAt(i));
+ }
+ }
+
+ @NonNull
+ public static final Creator<DeviceState.Configuration> CREATOR = new Creator<>() {
+ @Override
+ public DeviceState.Configuration createFromParcel(Parcel source) {
+ int identifier = source.readInt();
+ String name = source.readString8();
+ ArraySet<@DeviceStateProperties Integer> systemProperties = new ArraySet<>();
+ int systemPropertySize = source.readInt();
+ for (int i = 0; i < systemPropertySize; i++) {
+ systemProperties.add(source.readInt());
+ }
+ ArraySet<@DeviceStateProperties Integer> physicalProperties = new ArraySet<>();
+ int physicalPropertySize = source.readInt();
+ for (int j = 0; j < physicalPropertySize; j++) {
+ physicalProperties.add(source.readInt());
+ }
+ return new DeviceState.Configuration(identifier, name, systemProperties,
+ physicalProperties);
+ }
+
+ @Override
+ public DeviceState.Configuration[] newArray(int size) {
+ return new DeviceState.Configuration[size];
+ }
+ };
+
+ /** @hide */
+ public static class Builder {
+ private final int mIdentifier;
+ private final String mName;
+ private Set<@SystemDeviceStateProperties Integer> mSystemProperties =
+ Collections.emptySet();
+ private Set<@PhysicalDeviceStateProperties Integer> mPhysicalProperties =
+ Collections.emptySet();
+
+ public Builder(int identifier, String name) {
+ mIdentifier = identifier;
+ mName = name;
+ }
+
+ /** Sets the system properties for this {@link DeviceState.Configuration.Builder} */
+ public Builder setSystemProperties(
+ Set<@SystemDeviceStateProperties Integer> systemProperties) {
+ mSystemProperties = systemProperties;
+ return this;
+ }
+
+ /** Sets the system properties for this {@link DeviceState.Configuration.Builder} */
+ public Builder setPhysicalProperties(
+ Set<@PhysicalDeviceStateProperties Integer> physicalProperties) {
+ mPhysicalProperties = physicalProperties;
+ return this;
+ }
+
+ /**
+ * Returns a new {@link DeviceState.Configuration} whose values match the values set on
+ * the builder.
+ */
+ public DeviceState.Configuration build() {
+ return new DeviceState.Configuration(mIdentifier, mName, mSystemProperties,
+ mPhysicalProperties);
+ }
+ }
+ }
}
diff --git a/core/java/android/hardware/devicestate/DeviceStateInfo.java b/core/java/android/hardware/devicestate/DeviceStateInfo.java
index bc6af37..c319c89 100644
--- a/core/java/android/hardware/devicestate/DeviceStateInfo.java
+++ b/core/java/android/hardware/devicestate/DeviceStateInfo.java
@@ -24,7 +24,8 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -56,19 +57,19 @@
* The list of states supported by the device.
*/
@NonNull
- public final int[] supportedStates;
+ public final ArrayList<DeviceState> supportedStates;
/**
* The base (non-override) state of the device. The base state is the state of the device
* ignoring any override requests made through a call to {@link DeviceStateManager#requestState(
* DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
*/
- public final int baseState;
+ public final DeviceState baseState;
/**
* The state of the device.
*/
- public final int currentState;
+ public final DeviceState currentState;
/**
* Creates a new instance of {@link DeviceStateInfo}.
@@ -76,8 +77,9 @@
* NOTE: Unlike {@link #DeviceStateInfo(DeviceStateInfo)}, this constructor does not copy the
* supplied parameters.
*/
- public DeviceStateInfo(@NonNull int[] supportedStates, int baseState, int state) {
- this.supportedStates = supportedStates;
+ public DeviceStateInfo(@NonNull List<DeviceState> supportedStates, DeviceState baseState,
+ DeviceState state) {
+ this.supportedStates = new ArrayList<>(supportedStates);
this.baseState = baseState;
this.currentState = state;
}
@@ -87,8 +89,7 @@
* the fields of the returned instance.
*/
public DeviceStateInfo(@NonNull DeviceStateInfo info) {
- this(Arrays.copyOf(info.supportedStates, info.supportedStates.length),
- info.baseState, info.currentState);
+ this(List.copyOf(info.supportedStates), info.baseState, info.currentState);
}
@Override
@@ -96,15 +97,15 @@
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
DeviceStateInfo that = (DeviceStateInfo) other;
- return baseState == that.baseState
- && currentState == that.currentState
- && Arrays.equals(supportedStates, that.supportedStates);
+ return baseState.equals(that.baseState)
+ && currentState.equals(that.currentState)
+ && Objects.equals(supportedStates, that.supportedStates);
}
@Override
public int hashCode() {
int result = Objects.hash(baseState, currentState);
- result = 31 * result + Arrays.hashCode(supportedStates);
+ result = 31 * result + supportedStates.hashCode();
return result;
}
@@ -112,13 +113,13 @@
@ChangeFlags
public int diff(@NonNull DeviceStateInfo other) {
int diff = 0;
- if (!Arrays.equals(supportedStates, other.supportedStates)) {
+ if (!supportedStates.equals(other.supportedStates)) {
diff |= CHANGED_SUPPORTED_STATES;
}
- if (baseState != other.baseState) {
+ if (!baseState.equals(other.baseState)) {
diff |= CHANGED_BASE_STATE;
}
- if (currentState != other.currentState) {
+ if (!currentState.equals(other.currentState)) {
diff |= CHANGED_CURRENT_STATE;
}
return diff;
@@ -126,13 +127,13 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(supportedStates.length);
- for (int i = 0; i < supportedStates.length; i++) {
- dest.writeInt(supportedStates[i]);
+ dest.writeInt(supportedStates.size());
+ for (int i = 0; i < supportedStates.size(); i++) {
+ dest.writeTypedObject(supportedStates.get(i).getConfiguration(), flags);
}
- dest.writeInt(baseState);
- dest.writeInt(currentState);
+ dest.writeTypedObject(baseState.getConfiguration(), flags);
+ dest.writeTypedObject(currentState.getConfiguration(), flags);
}
@Override
@@ -140,16 +141,21 @@
return 0;
}
- public static final @NonNull Creator<DeviceStateInfo> CREATOR = new Creator<DeviceStateInfo>() {
+ public static final @NonNull Creator<DeviceStateInfo> CREATOR = new Creator<>() {
@Override
public DeviceStateInfo createFromParcel(Parcel source) {
final int numberOfSupportedStates = source.readInt();
- final int[] supportedStates = new int[numberOfSupportedStates];
+ final ArrayList<DeviceState> supportedStates = new ArrayList<>(numberOfSupportedStates);
for (int i = 0; i < numberOfSupportedStates; i++) {
- supportedStates[i] = source.readInt();
+ DeviceState.Configuration configuration = source.readTypedObject(
+ DeviceState.Configuration.CREATOR);
+ supportedStates.add(i, new DeviceState(configuration));
}
- final int baseState = source.readInt();
- final int currentState = source.readInt();
+
+ final DeviceState baseState = new DeviceState(
+ source.readTypedObject(DeviceState.Configuration.CREATOR));
+ final DeviceState currentState = new DeviceState(
+ source.readTypedObject(DeviceState.Configuration.CREATOR));
return new DeviceStateInfo(supportedStates, baseState, currentState);
}
diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
index 8b4d43e..a4c3833 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManager.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -49,7 +49,7 @@
*
* @hide
*/
- public static final int INVALID_DEVICE_STATE = -1;
+ public static final int INVALID_DEVICE_STATE_IDENTIFIER = -1;
/**
* The minimum allowed device state identifier.
diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
index 6db5aee..d6cc00d 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
@@ -19,7 +19,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
-import android.app.ActivityThread;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
import android.os.Binder;
@@ -33,13 +32,9 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
-import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
import java.util.concurrent.Executor;
/**
@@ -55,18 +50,12 @@
private static final String TAG = "DeviceStateManagerGlobal";
private static final boolean DEBUG = Build.IS_DEBUGGABLE;
- // TODO(b/325124054): Remove when system server refactor is completed
- private static int[] sFoldedDeviceStates = new int[0];
-
/**
* Returns an instance of {@link DeviceStateManagerGlobal}. May return {@code null} if a
* connection with the device state service couldn't be established.
*/
@Nullable
public static DeviceStateManagerGlobal getInstance() {
- // TODO(b/325124054): Remove when system server refactor is completed
- instantiateFoldedStateArray();
-
synchronized (DeviceStateManagerGlobal.class) {
if (sInstance == null) {
IBinder b = ServiceManager.getService(Context.DEVICE_STATE_SERVICE);
@@ -79,16 +68,6 @@
}
}
- // TODO(b/325124054): Remove when system server refactor is completed
- // TODO(b/325330654): Investigate if we need a Context passed in to DSMGlobal
- private static void instantiateFoldedStateArray() {
- Context context = ActivityThread.currentApplication();
- if (context != null) {
- sFoldedDeviceStates = context.getResources().getIntArray(
- com.android.internal.R.array.config_foldedDeviceStates);
- }
- }
-
private final Object mLock = new Object();
@NonNull
private final IDeviceStateManager mDeviceStateManager;
@@ -115,6 +94,7 @@
*
* @see DeviceStateManager#getSupportedStates()
*/
+ // TODO(b/325124054): Remove unused methods when clients are migrated.
public int[] getSupportedStates() {
synchronized (mLock) {
final DeviceStateInfo currentInfo;
@@ -132,12 +112,12 @@
}
}
- return Arrays.copyOf(currentInfo.supportedStates, currentInfo.supportedStates.length);
+ return getSupportedStateIdentifiersLocked(currentInfo.supportedStates);
}
}
/**
- * Returns the {@link List} of supported device states.
+ * Returns {@link List} of supported {@link DeviceState}s.
*
* @see DeviceStateManager#getSupportedDeviceStates()
*/
@@ -158,7 +138,7 @@
}
}
- return createDeviceStateList(currentInfo.supportedStates);
+ return List.copyOf(currentInfo.supportedStates);
}
}
@@ -285,13 +265,14 @@
if (mLastReceivedInfo != null) {
// Copy the array to prevent the callback from modifying the internal state.
- final int[] supportedStates = Arrays.copyOf(mLastReceivedInfo.supportedStates,
- mLastReceivedInfo.supportedStates.length);
+ final int[] supportedStates = getSupportedStateIdentifiersLocked(
+ mLastReceivedInfo.supportedStates);
wrapper.notifySupportedStatesChanged(supportedStates);
- wrapper.notifySupportedDeviceStatesChanged(createDeviceStateList(supportedStates));
- wrapper.notifyBaseStateChanged(mLastReceivedInfo.baseState);
- wrapper.notifyStateChanged(mLastReceivedInfo.currentState);
- wrapper.notifyDeviceStateChanged(createDeviceState(mLastReceivedInfo.currentState));
+ wrapper.notifySupportedDeviceStatesChanged(
+ List.copyOf(mLastReceivedInfo.supportedStates));
+ wrapper.notifyBaseStateChanged(mLastReceivedInfo.baseState.getIdentifier());
+ wrapper.notifyStateChanged(mLastReceivedInfo.currentState.getIdentifier());
+ wrapper.notifyDeviceStateChanged(mLastReceivedInfo.currentState);
}
}
}
@@ -349,6 +330,15 @@
return -1;
}
+ @GuardedBy("mLock")
+ private int[] getSupportedStateIdentifiersLocked(List<DeviceState> states) {
+ int[] identifiers = new int[states.size()];
+ for (int i = 0; i < states.size(); i++) {
+ identifiers[i] = states.get(i).getIdentifier();
+ }
+ return identifiers;
+ }
+
@Nullable
private IBinder findRequestTokenLocked(@NonNull DeviceStateRequest request) {
for (int i = 0; i < mRequests.size(); i++) {
@@ -363,32 +353,31 @@
private void handleDeviceStateInfoChanged(@NonNull DeviceStateInfo info) {
ArrayList<DeviceStateCallbackWrapper> callbacks;
DeviceStateInfo oldInfo;
+ int[] supportedStateIdentifiers;
synchronized (mLock) {
oldInfo = mLastReceivedInfo;
mLastReceivedInfo = info;
callbacks = new ArrayList<>(mCallbacks);
+ supportedStateIdentifiers = getSupportedStateIdentifiersLocked(info.supportedStates);
}
final int diff = oldInfo == null ? ~0 : info.diff(oldInfo);
if ((diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES) > 0) {
for (int i = 0; i < callbacks.size(); i++) {
- // Copy the array to prevent callbacks from modifying the internal state.
- final int[] supportedStates = Arrays.copyOf(info.supportedStates,
- info.supportedStates.length);
- callbacks.get(i).notifySupportedStatesChanged(supportedStates);
callbacks.get(i).notifySupportedDeviceStatesChanged(
- createDeviceStateList(supportedStates));
+ List.copyOf(info.supportedStates));
+ callbacks.get(i).notifySupportedStatesChanged(supportedStateIdentifiers);
}
}
if ((diff & DeviceStateInfo.CHANGED_BASE_STATE) > 0) {
for (int i = 0; i < callbacks.size(); i++) {
- callbacks.get(i).notifyBaseStateChanged(info.baseState);
+ callbacks.get(i).notifyBaseStateChanged(info.baseState.getIdentifier());
}
}
if ((diff & DeviceStateInfo.CHANGED_CURRENT_STATE) > 0) {
for (int i = 0; i < callbacks.size(); i++) {
- callbacks.get(i).notifyStateChanged(info.currentState);
- callbacks.get(i).notifyDeviceStateChanged(createDeviceState(info.currentState));
+ callbacks.get(i).notifyDeviceStateChanged(info.currentState);
+ callbacks.get(i).notifyStateChanged(info.currentState.getIdentifier());
}
}
}
@@ -421,36 +410,6 @@
}
}
- /**
- * Creates a {@link DeviceState} object from a device state identifier, with the
- * {@link DeviceState} property that corresponds to what display is primary.
- *
- */
- // TODO(b/325124054): Remove when system server refactor is completed
- @NonNull
- private DeviceState createDeviceState(int stateIdentifier) {
- final Set<@DeviceState.DeviceStateProperties Integer> properties = new HashSet<>();
- if (ArrayUtils.contains(sFoldedDeviceStates, stateIdentifier)) {
- properties.add(DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY);
- } else {
- properties.add(DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY);
- }
- return new DeviceState(stateIdentifier, "" /* name */, properties);
- }
-
- /**
- * Creates a list of {@link DeviceState} objects from an array of state identifiers.
- */
- // TODO(b/325124054): Remove when system server refactor is completed
- @NonNull
- private List<DeviceState> createDeviceStateList(int[] supportedStates) {
- List<DeviceState> deviceStateList = new ArrayList<>();
- for (int i = 0; i < supportedStates.length; i++) {
- deviceStateList.add(createDeviceState(supportedStates[i]));
- }
- return deviceStateList;
- }
-
private final class DeviceStateManagerCallback extends IDeviceStateManagerCallback.Stub {
@Override
public void onDeviceStateInfoChanged(DeviceStateInfo info) {
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 6b2814e..58aafbc 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -778,6 +778,16 @@
*/
float[] getAutoBrightnessLuxLevels(int mode);
+ /**
+ * @return The current brightness setting
+ */
+ float getBrightness();
+
+ /**
+ * @return The brightness value that is used when the device is in doze
+ */
+ float getDozeBrightness();
+
/** Returns whether displayoffload supports the given display state. */
static boolean isSupportedOffloadState(int displayState) {
return Display.isSuspendedState(displayState);
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 81e321d..b0f69f5 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -83,7 +83,8 @@
/**
* A class that coordinates access to the fingerprint hardware.
- * @deprecated See {@link BiometricPrompt} which shows a system-provided dialog upon starting
+ *
+ * @removed See {@link BiometricPrompt} which shows a system-provided dialog upon starting
* authentication. In a world where devices may have different types of biometric authentication,
* it's much more realistic to have a system-provided authentication dialog since the method may
* vary by vendor/device.
@@ -94,7 +95,6 @@
@RequiresFeature(PackageManager.FEATURE_FINGERPRINT)
public class FingerprintManager implements BiometricAuthenticator, BiometricFingerprintConstants {
private static final String TAG = "FingerprintManager";
- private static final boolean DEBUG = true;
private static final int MSG_ENROLL_RESULT = 100;
private static final int MSG_ACQUIRED = 101;
private static final int MSG_AUTHENTICATION_SUCCEEDED = 102;
@@ -196,6 +196,7 @@
/**
* Retrieves a test session for FingerprintManager.
+ *
* @hide
*/
@TestApi
@@ -254,9 +255,10 @@
}
/**
- * A wrapper class for the crypto objects supported by FingerprintManager. Currently the
+ * A wrapper class for the crypto objects supported by FingerprintManager. Currently, the
* framework supports {@link Signature}, {@link Cipher} and {@link Mac} objects.
- * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}
+ *
+ * @removed See {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}
*/
@Deprecated
public static final class CryptoObject extends android.hardware.biometrics.CryptoObject {
@@ -330,7 +332,8 @@
/**
* Container for callback data from {@link FingerprintManager#authenticate(CryptoObject,
* CancellationSignal, int, AuthenticationCallback, Handler)}.
- * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationResult}
+ *
+ * @removed See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationResult}
*/
@Deprecated
public static class AuthenticationResult {
@@ -392,7 +395,8 @@
* FingerprintManager#authenticate(CryptoObject, CancellationSignal,
* int, AuthenticationCallback, Handler) } must provide an implementation of this for listening to
* fingerprint events.
- * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback}
+ *
+ * @removed See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback}
*/
@Deprecated
public static abstract class AuthenticationCallback
@@ -455,6 +459,7 @@
/**
* Callback structure provided for {@link #detectFingerprint(CancellationSignal,
* FingerprintDetectionCallback, int, Surface)}.
+ *
* @hide
*/
public interface FingerprintDetectionCallback {
@@ -608,7 +613,8 @@
* by <a href="{@docRoot}training/articles/keystore.html">Android Keystore
* facility</a>.
* @throws IllegalStateException if the crypto primitive is not initialized.
- * @deprecated See {@link BiometricPrompt#authenticate(CancellationSignal, Executor,
+ *
+ * @removed See {@link BiometricPrompt#authenticate(CancellationSignal, Executor,
* BiometricPrompt.AuthenticationCallback)} and {@link BiometricPrompt#authenticate(
* BiometricPrompt.CryptoObject, CancellationSignal, Executor,
* BiometricPrompt.AuthenticationCallback)}
@@ -623,6 +629,7 @@
/**
* Per-user version of authenticate.
* @deprecated use {@link #authenticate(CryptoObject, CancellationSignal, AuthenticationCallback, Handler, FingerprintAuthenticateOptions)}.
+ *
* @hide
*/
@Deprecated
@@ -635,6 +642,7 @@
/**
* Per-user and per-sensor version of authenticate.
* @deprecated use {@link #authenticate(CryptoObject, CancellationSignal, AuthenticationCallback, Handler, FingerprintAuthenticateOptions)}.
+ *
* @hide
*/
@Deprecated
@@ -651,6 +659,7 @@
/**
* Version of authenticate with additional options.
+ *
* @hide
*/
@RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT})
@@ -698,6 +707,7 @@
/**
* Uses the fingerprint hardware to detect for the presence of a finger, without giving details
* about accept/reject/lockout.
+ *
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
@@ -740,6 +750,7 @@
* @param callback an object to receive enrollment events
* @param shouldLogMetrics a flag that indicates if enrollment failure/success metrics
* should be logged.
+ *
* @hide
*/
@RequiresPermission(MANAGE_FINGERPRINT)
@@ -810,6 +821,7 @@
/**
* Same as {@link #generateChallenge(int, GenerateChallengeCallback)}, but assumes the first
* enumerated sensor.
+ *
* @hide
*/
@RequiresPermission(MANAGE_FINGERPRINT)
@@ -824,6 +836,7 @@
/**
* Revokes the specified challenge.
+ *
* @hide
*/
@RequiresPermission(MANAGE_FINGERPRINT)
@@ -849,6 +862,7 @@
* @param sensorId Sensor ID that this operation takes effect for
* @param userId User ID that this operation takes effect for.
* @param hardwareAuthToken An opaque token returned by password confirmation.
+ *
* @hide
*/
@RequiresPermission(RESET_FINGERPRINT_LOCKOUT)
@@ -886,6 +900,7 @@
/**
* Removes all fingerprint templates for the given user.
+ *
* @hide
*/
@RequiresPermission(MANAGE_FINGERPRINT)
@@ -1005,6 +1020,7 @@
/**
* Forwards BiometricStateListener to FingerprintService
* @param listener new BiometricStateListener being added
+ *
* @hide
*/
public void registerBiometricStateListener(@NonNull BiometricStateListener listener) {
@@ -1156,7 +1172,8 @@
}
/**
- * This is triggered by SideFpsEventHandler
+ * This is triggered by SideFpsEventHandler.
+ *
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
@@ -1169,7 +1186,8 @@
* Determine if there is at least one fingerprint enrolled.
*
* @return true if at least one fingerprint is enrolled, false otherwise
- * @deprecated See {@link BiometricPrompt} and
+ *
+ * @removed See {@link BiometricPrompt} and
* {@link FingerprintManager#FINGERPRINT_ERROR_NO_FINGERPRINTS}
*/
@Deprecated
@@ -1203,7 +1221,8 @@
* Determine if fingerprint hardware is present and functional.
*
* @return true if hardware is present and functional, false otherwise.
- * @deprecated See {@link BiometricPrompt} and
+ *
+ * @removed See {@link BiometricPrompt} and
* {@link FingerprintManager#FINGERPRINT_ERROR_HW_UNAVAILABLE}
*/
@Deprecated
@@ -1229,6 +1248,7 @@
/**
* Get statically configured sensor properties.
+ *
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
@@ -1247,6 +1267,7 @@
/**
* Returns whether the device has a power button fingerprint sensor.
* @return boolean indicating whether power button is fingerprint sensor
+ *
* @hide
*/
public boolean isPowerbuttonFps() {
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 62473c5..4328d9f 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -28,6 +28,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
+import android.app.AppGlobals;
import android.content.Context;
import android.os.UserHandle;
import android.provider.Settings;
@@ -393,15 +394,34 @@
*
* @hide
*/
- public static boolean isStylusPointerIconEnabled(@NonNull Context context) {
+ public static boolean isStylusPointerIconEnabled(@NonNull Context context,
+ boolean forceReloadSetting) {
if (InputProperties.force_enable_stylus_pointer_icon().orElse(false)) {
+ // Sysprop override is set
return true;
}
- return context.getResources()
- .getBoolean(com.android.internal.R.bool.config_enableStylusPointerIcon)
- && Settings.Secure.getIntForUser(context.getContentResolver(),
- Settings.Secure.STYLUS_POINTER_ICON_ENABLED,
- DEFAULT_STYLUS_POINTER_ICON_ENABLED, UserHandle.USER_CURRENT_OR_SELF) != 0;
+ if (!context.getResources().getBoolean(
+ com.android.internal.R.bool.config_enableStylusPointerIcon)) {
+ // Stylus pointer icons are disabled for the build
+ return false;
+ }
+ if (forceReloadSetting) {
+ return Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.STYLUS_POINTER_ICON_ENABLED,
+ DEFAULT_STYLUS_POINTER_ICON_ENABLED, UserHandle.USER_CURRENT_OR_SELF) != 0;
+ }
+ return AppGlobals.getIntCoreSetting(Settings.Secure.STYLUS_POINTER_ICON_ENABLED,
+ DEFAULT_STYLUS_POINTER_ICON_ENABLED) != 0;
+ }
+
+ /**
+ * Whether a pointer icon will be shown over the location of a stylus pointer.
+ *
+ * @hide
+ * @see #isStylusPointerIconEnabled(Context, boolean)
+ */
+ public static boolean isStylusPointerIconEnabled(@NonNull Context context) {
+ return isStylusPointerIconEnabled(context, false /* forceReloadSetting */);
}
/**
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index a3a2a2e..c5167db 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -304,7 +304,11 @@
*
* @param id primary identifier of a program to fetch
* @return the program info, or null if there is no such program on the list
+ *
+ * @deprecated Use {@link #getProgramInfos(ProgramSelector.Identifier)} to get all programs
+ * with the given primary identifier
*/
+ @Deprecated
public @Nullable RadioManager.ProgramInfo get(@NonNull ProgramSelector.Identifier id) {
Map<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries;
synchronized (mLock) {
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index a968c6f..0740374 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -312,15 +312,23 @@
public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10;
/**
* 1: AM, 2:FM
+ * @deprecated use {@link #IDENTIFIER_TYPE_DRMO_FREQUENCY} instead
*/
+ @Deprecated
public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11;
/**
* 32bit primary identifier for SiriusXM Satellite Radio.
+ *
+ * @deprecated SiriusXM Satellite Radio is not supported
*/
+ @Deprecated
public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12;
/**
* 0-999 range
+ *
+ * @deprecated SiriusXM Satellite Radio is not supported
*/
+ @Deprecated
public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13;
/**
* 44bit compound primary identifier for Digital Audio Broadcasting and
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 61cf8901..da6c686 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -166,7 +166,12 @@
* analog handover state managed from the HAL implementation side.
*
* <p>Some radio technologies may not support this, i.e. DAB.
+ *
+ * @deprecated Use {@link #CONFIG_FORCE_ANALOG_FM} instead. If {@link #CONFIG_FORCE_ANALOG_FM}
+ * is supported in HAL, {@link RadioTuner#setConfigFlag} and {@link RadioTuner#isConfigFlagSet}
+ * with CONFIG_FORCE_ANALOG will set/get the value of {@link #CONFIG_FORCE_ANALOG_FM}.
*/
+ @Deprecated
public static final int CONFIG_FORCE_ANALOG = 2;
/**
* Forces the digital playback for the supporting radio technology.
diff --git a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
index 2a11835..9e487e1 100644
--- a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
+++ b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
@@ -25,5 +25,5 @@
name: "enable_usb_data_signal_staking"
namespace: "preload_safety"
description: "Enables signal API with staking"
- bug: "296119135"
+ bug: "287498482"
}
\ No newline at end of file
diff --git a/core/java/android/net/flags.aconfig b/core/java/android/net/flags.aconfig
index 9f9aef8..3544a69 100644
--- a/core/java/android/net/flags.aconfig
+++ b/core/java/android/net/flags.aconfig
@@ -19,6 +19,7 @@
flag {
name: "register_nsd_offload_engine"
+ is_exported: true
namespace: "android_core_networking"
description: "Flag for registerOffloadEngine API in NsdManager"
bug: "294777050"
diff --git a/core/java/android/net/thread/flags.aconfig b/core/java/android/net/thread/flags.aconfig
index d679f9c..ef798ad 100644
--- a/core/java/android/net/thread/flags.aconfig
+++ b/core/java/android/net/thread/flags.aconfig
@@ -5,6 +5,7 @@
flag {
name: "thread_user_restriction_enabled"
+ is_exported: true
namespace: "thread_network"
description: "Controls whether user restriction on thread networks is enabled"
bug: "307679182"
@@ -12,6 +13,7 @@
flag {
name: "thread_enabled_platform"
+ is_exported: true
namespace: "thread_network"
description: "Controls whether the Android Thread feature is enabled"
bug: "301473012"
diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig
index 7afd721..97b773e 100644
--- a/core/java/android/net/vcn/flags.aconfig
+++ b/core/java/android/net/vcn/flags.aconfig
@@ -2,6 +2,7 @@
flag {
name: "safe_mode_config"
+ is_exported: true
namespace: "vcn"
description: "Feature flag for safe mode configurability"
bug: "276358140"
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index b1c24a7..90279c6 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -103,3 +103,6 @@
# ProfilingService
per-file ProfilingServiceManager.java = file:/PERFORMANCE_OWNERS
+
+# Memory
+per-file OomKillRecord.java = file:/MEMORY_OWNERS
\ No newline at end of file
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index b669814..8a17742 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -1259,6 +1259,24 @@
}
/**
+ * Reboot into recovery and wipe the data partition with ext4
+ *
+ * @throws IOException if something goes wrong.
+ *
+ * @hide
+ */
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.RECOVERY,
+ android.Manifest.permission.REBOOT
+ })
+ public void wipePartitionToExt4()
+ throws IOException {
+ // Reformat /data partition with ext4
+ String command = "--wipe_data\n--reformat_data=ext4";
+ rebootRecoveryWithCommand(command);
+ }
+
+ /**
* Reboot into the recovery system with the supplied argument.
* @param args to pass to the recovery utility.
* @throws IOException if something goes wrong.
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index c0b4909..bebb912 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -164,6 +164,8 @@
private static native void nativeInstant(long tag, String name);
@FastNative
private static native void nativeInstantForTrack(long tag, String trackName, String name);
+ @FastNative
+ private static native void nativeRegisterWithPerfetto();
private Trace() {
}
@@ -523,4 +525,15 @@
nativeTraceCounter(TRACE_TAG_APP, counterName, counterValue);
}
}
+
+ /**
+ * Initialize the perfetto SDK. This must be called before any tracing
+ * calls so that perfetto SDK can be used, otherwise libcutils would be
+ * used.
+ *
+ * @hide
+ */
+ public static void registerWithPerfetto() {
+ nativeRegisterWithPerfetto();
+ }
}
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index d9400ac..943014c 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -3,6 +3,7 @@
flag {
name: "android_os_build_vanilla_ice_cream"
+ is_exported: true
namespace: "build"
description: "Feature flag for adding the VANILLA_ICE_CREAM constant."
bug: "264658905"
@@ -10,6 +11,7 @@
flag {
name: "state_of_health_public"
+ is_exported: true
namespace: "system_sw_battery"
description: "Feature flag for making state_of_health a public api."
bug: "288842045"
@@ -132,3 +134,10 @@
is_fixed_read_only: true
bug: "324241334"
}
+
+flag {
+ namespace: "system_performance"
+ name: "perfetto_sdk_tracing"
+ description: "Tracing using Perfetto SDK."
+ bug: "303199244"
+}
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 5e7edda..3c77c44 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -2,6 +2,7 @@
flag {
name: "certificate_transparency_configuration"
+ is_exported: true
namespace: "network_security"
description: "Enable certificate transparency setting in the network security config"
bug: "28746284"
@@ -16,6 +17,7 @@
flag {
name: "mgf1_digest_setter_v2"
+ is_exported: true
namespace: "hardware_backed_security"
description: "Feature flag for mgf1 digest setter in key generation and import parameters."
bug: "308378912"
@@ -32,6 +34,7 @@
flag {
name: "keyinfo_unlocked_device_required"
+ is_exported: true
namespace: "hardware_backed_security"
description: "Add the API android.security.keystore.KeyInfo#isUnlockedDeviceRequired()"
bug: "296475382"
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index dd8b3de..c489c58 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -38,6 +38,7 @@
boolean isDreaming();
@UnsupportedAppUsage
boolean isDreamingOrInPreview();
+ boolean canStartDreaming(boolean isScreenOn);
void finishSelf(in IBinder token, boolean immediate);
void startDozing(in IBinder token, int screenState, int screenBrightness);
void stopDozing(in IBinder token);
diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl b/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
index 73257ed..799c7545 100644
--- a/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
+++ b/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
@@ -20,7 +20,6 @@
import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.ITokenInfoCallback;
import android.app.ondeviceintelligence.IProcessingSignal;
-import android.app.ondeviceintelligence.Content;
import android.app.ondeviceintelligence.Feature;
import android.os.ICancellationSignal;
import android.os.PersistableBundle;
@@ -35,12 +34,12 @@
*/
oneway interface IOnDeviceSandboxedInferenceService {
void registerRemoteStorageService(in IRemoteStorageService storageService);
- void requestTokenInfo(int callerUid, in Feature feature, in Content request, in ICancellationSignal cancellationSignal,
+ void requestTokenInfo(int callerUid, in Feature feature, in Bundle request, in ICancellationSignal cancellationSignal,
in ITokenInfoCallback tokenInfoCallback);
- void processRequest(int callerUid, in Feature feature, in Content request, in int requestType,
+ void processRequest(int callerUid, in Feature feature, in Bundle request, in int requestType,
in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal,
in IResponseCallback callback);
- void processRequestStreaming(int callerUid, in Feature feature, in Content request, in int requestType,
+ void processRequestStreaming(int callerUid, in Feature feature, in Bundle request, in int requestType,
in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal,
in IStreamingResponseCallback callback);
void updateProcessingState(in Bundle processingState,
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
index fce3689..27e8628 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
@@ -32,7 +32,9 @@
import android.app.ondeviceintelligence.IFeatureCallback;
import android.app.ondeviceintelligence.IFeatureDetailsCallback;
import android.app.ondeviceintelligence.IListFeaturesCallback;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.InferenceParams;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
@@ -48,14 +50,8 @@
import com.android.internal.infra.AndroidFuture;
-import androidx.annotation.IntDef;
-
import java.io.File;
import java.io.FileNotFoundException;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -232,9 +228,9 @@
* @param callbackExecutor executor to the run status callback on.
* @param statusReceiver receiver to get status of the update state operation.
*/
- public final void updateProcessingState(@NonNull Bundle processingState,
+ public final void updateProcessingState(@NonNull @InferenceParams Bundle processingState,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull OutcomeReceiver<PersistableBundle, OnDeviceUpdateProcessingException> statusReceiver) {
+ @NonNull OutcomeReceiver<PersistableBundle, OnDeviceIntelligenceException> statusReceiver) {
Objects.requireNonNull(callbackExecutor);
if (mRemoteProcessingService == null) {
throw new IllegalStateException("Remote processing service is unavailable.");
@@ -254,7 +250,7 @@
public void onFailure(int errorCode, String errorMessage) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> statusReceiver.onError(
- new OnDeviceUpdateProcessingException(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage))));
}
});
@@ -265,7 +261,7 @@
}
private OutcomeReceiver<Feature,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapFeatureCallback(
+ OnDeviceIntelligenceException> wrapFeatureCallback(
IFeatureCallback featureCallback) {
return new OutcomeReceiver<>() {
@Override
@@ -279,7 +275,7 @@
@Override
public void onError(
- @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) {
+ @NonNull OnDeviceIntelligenceException exception) {
try {
featureCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
@@ -291,7 +287,7 @@
}
private OutcomeReceiver<List<Feature>,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapListFeaturesCallback(
+ OnDeviceIntelligenceException> wrapListFeaturesCallback(
IListFeaturesCallback listFeaturesCallback) {
return new OutcomeReceiver<>() {
@Override
@@ -305,7 +301,7 @@
@Override
public void onError(
- @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) {
+ @NonNull OnDeviceIntelligenceException exception) {
try {
listFeaturesCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
@@ -317,7 +313,7 @@
}
private OutcomeReceiver<FeatureDetails,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapFeatureDetailsCallback(
+ OnDeviceIntelligenceException> wrapFeatureDetailsCallback(
IFeatureDetailsCallback featureStatusCallback) {
return new OutcomeReceiver<>() {
@Override
@@ -331,7 +327,7 @@
@Override
public void onError(
- @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) {
+ @NonNull OnDeviceIntelligenceException exception) {
try {
featureStatusCallback.onFailure(exception.getErrorCode(),
exception.getMessage(), exception.getErrorParams());
@@ -444,7 +440,7 @@
*/
public abstract void onGetFeatureDetails(int callerUid, @NonNull Feature feature,
@NonNull OutcomeReceiver<FeatureDetails,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureDetailsCallback);
+ OnDeviceIntelligenceException> featureDetailsCallback);
/**
@@ -455,7 +451,7 @@
*/
public abstract void onGetFeature(int callerUid, int featureId,
@NonNull OutcomeReceiver<Feature,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureCallback);
+ OnDeviceIntelligenceException> featureCallback);
/**
* List all features which are available in the remote implementation. The implementation might
@@ -465,7 +461,7 @@
* @param listFeaturesCallback callback to populate the features list.
*/
public abstract void onListFeatures(int callerUid, @NonNull OutcomeReceiver<List<Feature>,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> listFeaturesCallback);
+ OnDeviceIntelligenceException> listFeaturesCallback);
/**
* Provides a long value representing the version of the remote implementation processing
@@ -474,60 +470,4 @@
* @param versionConsumer consumer to populate the version.
*/
public abstract void onGetVersion(@NonNull LongConsumer versionConsumer);
-
-
- /**
- * Exception type to be populated when calls to {@link #updateProcessingState} fail.
- */
- public static class OnDeviceUpdateProcessingException extends
- OnDeviceIntelligenceServiceException {
- /**
- * The connection to remote service failed and the processing state could not be updated.
- */
- public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 1;
-
-
- /**
- * @hide
- */
- @IntDef(value = {
- PROCESSING_UPDATE_STATUS_CONNECTION_FAILED
- }, open = true)
- @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER,
- ElementType.FIELD})
- @Retention(RetentionPolicy.SOURCE)
- public @interface ErrorCode {
- }
-
- public OnDeviceUpdateProcessingException(@ErrorCode int errorCode) {
- super(errorCode);
- }
-
- public OnDeviceUpdateProcessingException(@ErrorCode int errorCode,
- @NonNull String errorMessage) {
- super(errorCode, errorMessage);
- }
- }
-
- /**
- * Exception type to be used for surfacing errors to service implementation.
- */
- public abstract static class OnDeviceIntelligenceServiceException extends Exception {
- private final int mErrorCode;
-
- public OnDeviceIntelligenceServiceException(int errorCode) {
- this.mErrorCode = errorCode;
- }
-
- public OnDeviceIntelligenceServiceException(int errorCode,
- @NonNull String errorMessage) {
- super(errorMessage);
- this.mErrorCode = errorCode;
- }
-
- public int getErrorCode() {
- return mErrorCode;
- }
-
- }
}
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
index 7f7f9c2..d943c80 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
@@ -27,20 +27,21 @@
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.Service;
-import android.app.ondeviceintelligence.Content;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
+import android.os.Bundle;
import android.app.ondeviceintelligence.Feature;
import android.app.ondeviceintelligence.IProcessingSignal;
import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.IStreamingResponseCallback;
import android.app.ondeviceintelligence.ITokenInfoCallback;
import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.InferenceParams;
import android.app.ondeviceintelligence.ProcessingSignal;
-import android.app.ondeviceintelligence.ProcessingOutcomeReceiver;
-import android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver;
+import android.app.ondeviceintelligence.ProcessingCallback;
+import android.app.ondeviceintelligence.StreamingProcessingCallback;
import android.app.ondeviceintelligence.TokenInfo;
import android.content.Context;
import android.content.Intent;
-import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.HandlerExecutor;
@@ -51,7 +52,6 @@
import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.RemoteException;
-import android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException;
import android.util.Log;
import android.util.Slog;
@@ -121,7 +121,7 @@
}
@Override
- public void requestTokenInfo(int callerUid, Feature feature, Content request,
+ public void requestTokenInfo(int callerUid, Feature feature, Bundle request,
ICancellationSignal cancellationSignal,
ITokenInfoCallback tokenInfoCallback) {
Objects.requireNonNull(feature);
@@ -134,7 +134,7 @@
}
@Override
- public void processRequestStreaming(int callerUid, Feature feature, Content request,
+ public void processRequestStreaming(int callerUid, Feature feature, Bundle request,
int requestType, ICancellationSignal cancellationSignal,
IProcessingSignal processingSignal,
IStreamingResponseCallback callback) {
@@ -151,7 +151,7 @@
}
@Override
- public void processRequest(int callerUid, Feature feature, Content request,
+ public void processRequest(int callerUid, Feature feature, Bundle request,
int requestType, ICancellationSignal cancellationSignal,
IProcessingSignal processingSignal,
IResponseCallback callback) {
@@ -198,18 +198,17 @@
@NonNull
public abstract void onTokenInfoRequest(
int callerUid, @NonNull Feature feature,
- @NonNull Content request,
+ @NonNull @InferenceParams Bundle request,
@Nullable CancellationSignal cancellationSignal,
- @NonNull OutcomeReceiver<TokenInfo,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback);
+ @NonNull OutcomeReceiver<TokenInfo, OnDeviceIntelligenceException> callback);
/**
* Invoked when caller provides a request for a particular feature to be processed in a
* streaming manner. The expectation from the implementation is that when processing the
* request,
- * it periodically populates the {@link StreamedProcessingOutcomeReceiver#onNewContent} to continuously
- * provide partial Content results for the caller to utilize. Optionally the implementation can
- * provide the complete response in the {@link StreamedProcessingOutcomeReceiver#onResult} upon
+ * it periodically populates the {@link StreamingProcessingCallback#onPartialResult} to continuously
+ * provide partial Bundle results for the caller to utilize. Optionally the implementation can
+ * provide the complete response in the {@link StreamingProcessingCallback#onResult} upon
* processing completion.
*
* @param callerUid UID of the caller that initiated this call chain.
@@ -225,11 +224,11 @@
@NonNull
public abstract void onProcessRequestStreaming(
int callerUid, @NonNull Feature feature,
- @Nullable Content request,
+ @NonNull @InferenceParams Bundle request,
@OnDeviceIntelligenceManager.RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
- @NonNull StreamedProcessingOutcomeReceiver callback);
+ @NonNull StreamingProcessingCallback callback);
/**
* Invoked when caller provides a request for a particular feature to be processed in one shot
@@ -251,11 +250,11 @@
@NonNull
public abstract void onProcessRequest(
int callerUid, @NonNull Feature feature,
- @Nullable Content request,
+ @NonNull @InferenceParams Bundle request,
@OnDeviceIntelligenceManager.RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
- @NonNull ProcessingOutcomeReceiver callback);
+ @NonNull ProcessingCallback callback);
/**
@@ -267,9 +266,9 @@
* @param callback callback to populate the update status and if there are params
* associated with the status.
*/
- public abstract void onUpdateProcessingState(@NonNull Bundle processingState,
+ public abstract void onUpdateProcessingState(@NonNull @InferenceParams Bundle processingState,
@NonNull OutcomeReceiver<PersistableBundle,
- OnDeviceUpdateProcessingException> callback);
+ OnDeviceIntelligenceException> callback);
/**
@@ -344,7 +343,7 @@
/**
* Returns the {@link Executor} to use for incoming IPC from request sender into your service
* implementation. For e.g. see
- * {@link ProcessingOutcomeReceiver#onDataAugmentRequest(Content,
+ * {@link ProcessingCallback#onDataAugmentRequest(Bundle,
* Consumer)} where we use the executor to populate the consumer.
* <p>
* Override this method in your {@link OnDeviceSandboxedInferenceService} implementation to
@@ -380,13 +379,13 @@
});
}
- private ProcessingOutcomeReceiver wrapResponseCallback(
+ private ProcessingCallback wrapResponseCallback(
IResponseCallback callback) {
- return new ProcessingOutcomeReceiver() {
+ return new ProcessingCallback() {
@Override
- public void onResult(@androidx.annotation.NonNull Content response) {
+ public void onResult(@androidx.annotation.NonNull Bundle result) {
try {
- callback.onSuccess(response);
+ callback.onSuccess(result);
} catch (RemoteException e) {
Slog.e(TAG, "Error sending result: " + e);
}
@@ -394,7 +393,7 @@
@Override
public void onError(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
+ OnDeviceIntelligenceException exception) {
try {
callback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
@@ -404,8 +403,8 @@
}
@Override
- public void onDataAugmentRequest(@NonNull Content content,
- @NonNull Consumer<Content> contentCallback) {
+ public void onDataAugmentRequest(@NonNull Bundle content,
+ @NonNull Consumer<Bundle> contentCallback) {
try {
callback.onDataAugmentRequest(content, wrapRemoteCallback(contentCallback));
@@ -416,22 +415,22 @@
};
}
- private StreamedProcessingOutcomeReceiver wrapStreamingResponseCallback(
+ private StreamingProcessingCallback wrapStreamingResponseCallback(
IStreamingResponseCallback callback) {
- return new StreamedProcessingOutcomeReceiver() {
+ return new StreamingProcessingCallback() {
@Override
- public void onNewContent(@androidx.annotation.NonNull Content content) {
+ public void onPartialResult(@androidx.annotation.NonNull Bundle partialResult) {
try {
- callback.onNewContent(content);
+ callback.onNewContent(partialResult);
} catch (RemoteException e) {
Slog.e(TAG, "Error sending result: " + e);
}
}
@Override
- public void onResult(@androidx.annotation.NonNull Content response) {
+ public void onResult(@androidx.annotation.NonNull Bundle result) {
try {
- callback.onSuccess(response);
+ callback.onSuccess(result);
} catch (RemoteException e) {
Slog.e(TAG, "Error sending result: " + e);
}
@@ -439,7 +438,7 @@
@Override
public void onError(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
+ OnDeviceIntelligenceException exception) {
try {
callback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
@@ -449,8 +448,8 @@
}
@Override
- public void onDataAugmentRequest(@NonNull Content content,
- @NonNull Consumer<Content> contentCallback) {
+ public void onDataAugmentRequest(@NonNull Bundle content,
+ @NonNull Consumer<Bundle> contentCallback) {
try {
callback.onDataAugmentRequest(content, wrapRemoteCallback(contentCallback));
@@ -462,13 +461,13 @@
}
private RemoteCallback wrapRemoteCallback(
- @androidx.annotation.NonNull Consumer<Content> contentCallback) {
+ @androidx.annotation.NonNull Consumer<Bundle> contentCallback) {
return new RemoteCallback(
result -> {
if (result != null) {
getCallbackExecutor().execute(() -> contentCallback.accept(
result.getParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY,
- Content.class)));
+ Bundle.class)));
} else {
getCallbackExecutor().execute(
() -> contentCallback.accept(null));
@@ -476,8 +475,7 @@
});
}
- private OutcomeReceiver<TokenInfo,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapTokenInfoCallback(
+ private OutcomeReceiver<TokenInfo, OnDeviceIntelligenceException> wrapTokenInfoCallback(
ITokenInfoCallback tokenInfoCallback) {
return new OutcomeReceiver<>() {
@Override
@@ -491,7 +489,7 @@
@Override
public void onError(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
+ OnDeviceIntelligenceException exception) {
try {
tokenInfoCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
@@ -503,7 +501,7 @@
}
@NonNull
- private static OutcomeReceiver<PersistableBundle, OnDeviceUpdateProcessingException> wrapOutcomeReceiver(
+ private static OutcomeReceiver<PersistableBundle, OnDeviceIntelligenceException> wrapOutcomeReceiver(
IProcessingUpdateStatusCallback callback) {
return new OutcomeReceiver<>() {
@Override
@@ -518,7 +516,7 @@
@Override
public void onError(
- @androidx.annotation.NonNull OnDeviceUpdateProcessingException error) {
+ @androidx.annotation.NonNull OnDeviceIntelligenceException error) {
try {
callback.onFailure(error.getErrorCode(), error.getMessage());
} catch (RemoteException e) {
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index a08264e..ccc17ec 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -819,19 +819,15 @@
/**
* Called when the keyphrase is spoken.
*
- * <p>This implicitly stops listening for the keyphrase once it's detected. Clients should
- * start a recognition again once they are done handling this detection.
+ * <p>If {@code eventPayload.isRecognitionStopped()} returns true, this implicitly stops
+ * listening for the keyphrase once it's detected. Clients should start a recognition again
+ * once they are done handling this detection.
*
* @param eventPayload Payload data for the detection event. This may contain the trigger
* audio, if requested when calling {@link
- * AlwaysOnHotwordDetector#startRecognition(int)}.
+ * AlwaysOnHotwordDetector#startRecognition(int)} or if the audio comes from the {@link
+ * android.service.wearable.WearableSensingService}.
*/
- // TODO(b/324635656): Update Javadoc for 24Q3 release:
- // 1. Prepend to the first paragraph:
- // If {@code eventPayload.isRecognitionStopped()} returns true, this...
- // 2. Append to the description for @param eventPayload:
- // ...or if the audio comes from {@link
- // android.service.wearable.WearableSensingService}.
public abstract void onDetected(@NonNull EventPayload eventPayload);
/**
diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java
index 6140812..df4a2bb 100644
--- a/core/java/android/service/voice/HotwordDetectedResult.java
+++ b/core/java/android/service/voice/HotwordDetectedResult.java
@@ -662,6 +662,8 @@
/**
* Id of the current speaker
+ *
+ * <p>Only values between 0 and {@link #getMaxSpeakerId} (inclusive) are accepted.
*/
@DataClass.Generated.Member
@FlaggedApi(Flags.FLAG_ALLOW_SPEAKER_ID_EGRESS)
@@ -982,6 +984,8 @@
/**
* Id of the current speaker
+ *
+ * <p>Only values between 0 and {@link #getMaxSpeakerId} (inclusive) are accepted.
*/
@DataClass.Generated.Member
@FlaggedApi(Flags.FLAG_ALLOW_SPEAKER_ID_EGRESS)
@@ -1228,7 +1232,7 @@
}
@DataClass.Generated(
- time = 1704944087827L,
+ time = 1709773165191L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java",
inputSignatures = "public static final int CONFIDENCE_LEVEL_NONE\npublic static final int CONFIDENCE_LEVEL_LOW\npublic static final int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final int CONFIDENCE_LEVEL_HIGH\npublic static final int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final int HOTWORD_OFFSET_UNSET\npublic static final int AUDIO_CHANNEL_UNSET\npublic static final int BACKGROUND_AUDIO_POWER_UNSET\nprivate static final int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE\nprivate static final java.lang.String EXTRA_PROXIMITY\npublic static final int PROXIMITY_UNKNOWN\npublic static final int PROXIMITY_NEAR\npublic static final int PROXIMITY_FAR\nprivate final int mSpeakerId\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate int mHotwordOffsetMillis\nprivate int mHotwordDurationMillis\nprivate int mAudioChannel\nprivate boolean mHotwordDetectionPersonalized\nprivate final int mScore\nprivate final int mPersonalizedScore\nprivate final int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static int sMaxBundleSize\nprivate final int mBackgroundAudioPower\nprivate static int defaultSpeakerId()\npublic static @android.annotation.FlaggedApi int getMaxSpeakerId()\nprivate static int defaultConfidenceLevel()\nprivate static int defaultScore()\nprivate static int defaultPersonalizedScore()\npublic static int getMaxScore()\nprivate static int defaultHotwordPhraseId()\npublic static int getMaxHotwordPhraseId()\nprivate static java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static android.os.PersistableBundle defaultExtras()\npublic static int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\nprivate static int defaultBackgroundAudioPower()\npublic static int getMaxBackgroundAudioPower()\npublic static int getParcelableSize(android.os.Parcelable)\npublic static int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static int bitCount(long)\nprivate void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\npublic void setProximity(double)\npublic @android.service.voice.HotwordDetectedResult.ProximityValue int getProximity()\nprivate @android.service.voice.HotwordDetectedResult.ProximityValue int convertToProximityLevel(double)\npublic android.service.voice.HotwordDetectedResult.Builder buildUpon()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []")
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index 60e9de7..937aecc 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -363,9 +363,11 @@
* {@link HotwordDetector#startRecognition(ParcelFileDescriptor, AudioFormat,
* PersistableBundle)} run} hotword recognition on audio coming from an external connected
* microphone.
- * <p>
- * Upon invoking the {@code callback}, the system closes {@code audioStream} and sends the
- * detection result to the {@link HotwordDetector.Callback hotword detector}.
+ *
+ * <p>Upon invoking the {@code callback}, the system will send the detection result to
+ * the {@link HotwordDetector}'s callback. If {@code
+ * options.getBoolean(KEY_SYSTEM_WILL_CLOSE_AUDIO_STREAM_AFTER_CALLBACK, true)} returns true,
+ * the system will also close the {@code audioStream} after {@code callback} is invoked.
*
* @param audioStream Stream containing audio bytes returned from a microphone
* @param audioFormat Format of the supplied audio
@@ -375,11 +377,6 @@
* PersistableBundle)}.
* @param callback The callback to use for responding to the detection request.
*/
- // TODO(b/324635656): Update Javadoc for 24Q3 release. Change the last paragraph to:
- // <p>Upon invoking the {@code callback}, the system will send the detection result to
- // the {@link HotwordDetector}'s callback. If {@code
- // options.getBoolean(KEY_SYSTEM_WILL_CLOSE_AUDIO_STREAM_AFTER_CALLBACK, true)} returns true,
- // the system will also close the {@code audioStream} after {@code callback} is invoked.
public void onDetect(
@NonNull ParcelFileDescriptor audioStream,
@NonNull AudioFormat audioFormat,
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index f68fcab9..aff1d4a 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -119,3 +119,10 @@
is_fixed_read_only: true
bug: "324676775"
}
+
+flag {
+ name: "handwriting_cursor_position"
+ namespace: "text"
+ description: "When handwriting is initiated in an unfocused TextView, cursor is placed at the end of the closest paragraph."
+ bug: "323376217"
+}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index b91a878..4d4cb6c 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -194,13 +194,6 @@
public static final String SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION =
"settings_remote_device_credential_validation";
- /**
- * Flag to enable/disable to start treating any calls to "suspend" an app as "quarantine".
- * @hide
- */
- public static final String SETTINGS_TREAT_PAUSE_AS_QUARANTINE =
- "settings_treat_pause_as_quarantine";
-
private static final Map<String, String> DEFAULT_FLAGS;
static {
@@ -246,7 +239,6 @@
DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS, "false");
// TODO: b/298454866 Replace with Trunk Stable Feature Flag
DEFAULT_FLAGS.put(SETTINGS_REMOTEAUTH_ENROLLMENT_SETTINGS, "false");
- DEFAULT_FLAGS.put(SETTINGS_TREAT_PAUSE_AS_QUARANTINE, "false");
}
private static final Set<String> PERSISTENT_FLAGS;
@@ -264,7 +256,6 @@
PERSISTENT_FLAGS.add(SETTINGS_ENABLE_SPA);
PERSISTENT_FLAGS.add(SETTINGS_ENABLE_SPA_PHASE2);
PERSISTENT_FLAGS.add(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM);
- PERSISTENT_FLAGS.add(SETTINGS_TREAT_PAUSE_AS_QUARANTINE);
}
/**
diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index a6724da..a4c3ed9 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -24,6 +24,7 @@
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import static android.util.apk.ApkSignatureSchemeV4Verifier.APK_SIGNATURE_SCHEME_DEFAULT;
+import android.annotation.NonNull;
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
import android.content.pm.SigningDetails.SignatureSchemeVersion;
@@ -33,9 +34,12 @@
import android.os.Build;
import android.os.Trace;
import android.os.incremental.V4Signature;
+import android.util.ArrayMap;
import android.util.Pair;
+import android.util.Slog;
import android.util.jar.StrictJarFile;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import libcore.io.IoUtils;
@@ -63,8 +67,14 @@
*/
public class ApkSignatureVerifier {
+ private static final String LOG_TAG = "ApkSignatureVerifier";
+
private static final AtomicReference<byte[]> sBuffer = new AtomicReference<>();
+ @GuardedBy("sOverrideSigningDetails")
+ private static final ArrayMap<SigningDetails, SigningDetails> sOverrideSigningDetails =
+ new ArrayMap<>();
+
/**
* Verifies the provided APK and returns the certificates associated with each signer.
*/
@@ -95,7 +105,54 @@
if (result.isError()) {
return input.error(result);
}
- return input.success(result.getResult().signingDetails);
+ SigningDetails signingDetails = result.getResult().signingDetails;
+ if (Build.isDebuggable()) {
+ SigningDetails overrideSigningDetails;
+ synchronized (sOverrideSigningDetails) {
+ overrideSigningDetails = sOverrideSigningDetails.get(signingDetails);
+ }
+ if (overrideSigningDetails != null) {
+ Slog.i(LOG_TAG, "Applying override signing details for APK " + apkPath);
+ signingDetails = overrideSigningDetails;
+ }
+ }
+ return input.success(signingDetails);
+ }
+
+ /**
+ * Add a pair of signing details so that packages signed with {@code oldSigningDetails} will
+ * behave as if they are signed by the {@code newSigningDetails}.
+ *
+ * @param oldSigningDetails the original signing detail of the package
+ * @param newSigningDetails the new signing detail that will replace the original one
+ */
+ public static void addOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails,
+ @NonNull SigningDetails newSigningDetails) {
+ synchronized (sOverrideSigningDetails) {
+ sOverrideSigningDetails.put(oldSigningDetails, newSigningDetails);
+ }
+ }
+
+ /**
+ * Remove a pair of signing details previously added via {@link #addOverrideSigningDetails} by
+ * the old signing details.
+ *
+ * @param oldSigningDetails the original signing detail of the package
+ * @throws SecurityException if the build is not debuggable
+ */
+ public static void removeOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails) {
+ synchronized (sOverrideSigningDetails) {
+ sOverrideSigningDetails.remove(oldSigningDetails);
+ }
+ }
+
+ /**
+ * Clear all pairs of signing details previously added via {@link #addOverrideSigningDetails}.
+ */
+ public static void clearOverrideSigningDetails() {
+ synchronized (sOverrideSigningDetails) {
+ sOverrideSigningDetails.clear();
+ }
}
/**
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 66655fc..29c8350 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -16,6 +16,8 @@
package android.view;
+import static com.android.text.flags.Flags.handwritingCursorPosition;
+
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -557,7 +559,8 @@
}
private void requestFocusWithoutReveal(View view) {
- if (view instanceof EditText editText && !mState.mStylusDownWithinEditorBounds) {
+ if (!handwritingCursorPosition() && view instanceof EditText editText
+ && !mState.mStylusDownWithinEditorBounds) {
// If the stylus down point was inside the EditText's bounds, then the EditText will
// automatically set its cursor position nearest to the stylus down point when it
// gains focus. If the stylus down point was outside the EditText's bounds (within
@@ -576,6 +579,17 @@
} else {
view.requestFocus();
}
+ if (handwritingCursorPosition() && view instanceof EditText editText) {
+ // Move the cursor to the end of the paragraph closest to the stylus down point.
+ view.getLocationInWindow(mTempLocation);
+ int line = editText.getLineAtCoordinate(mState.mStylusDownY - mTempLocation[1]);
+ int paragraphEnd = TextUtils.indexOf(editText.getText(), '\n',
+ editText.getLayout().getLineStart(line));
+ if (paragraphEnd < 0) {
+ paragraphEnd = editText.getText().length();
+ }
+ editText.setSelection(paragraphEnd);
+ }
}
/**
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 6cc4b20..1920fa3 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1876,11 +1876,15 @@
@Override
public @Appearance int getSystemBarsAppearance() {
+ @Appearance int appearance = mHost.getSystemBarsAppearance();
+
+ // We only return the requested appearance, not the implied one.
+ appearance &= ~APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS;
if (!mHost.isSystemBarsAppearanceControlled()) {
- // We only return the requested appearance, not the implied one.
- return 0;
+ appearance &= ~COMPATIBLE_APPEARANCE_FLAGS;
}
- return mHost.getSystemBarsAppearance();
+
+ return appearance;
}
@Override
diff --git a/core/java/android/view/InsetsFlags.java b/core/java/android/view/InsetsFlags.java
index 3355252..ca8a7a8 100644
--- a/core/java/android/view/InsetsFlags.java
+++ b/core/java/android/view/InsetsFlags.java
@@ -16,6 +16,7 @@
package android.view;
+import static android.view.WindowInsetsController.APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
@@ -64,7 +65,11 @@
@ViewDebug.FlagToString(
mask = APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS,
equals = APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS,
- name = "SEMI_TRANSPARENT_NAVIGATION_BARS")
+ name = "SEMI_TRANSPARENT_NAVIGATION_BARS"),
+ @ViewDebug.FlagToString(
+ mask = APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS,
+ equals = APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS,
+ name = "FORCE_LIGHT_NAVIGATION_BARS")
})
public @Appearance int appearance;
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index f9eba29..4ac78f5 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -355,11 +355,17 @@
final Rect frame = getFrame();
if (mBoundingRects == null) {
// No bounding rects set, make a single bounding rect that covers the intersection of
- // the |frame| and the |relativeFrame|.
+ // the |frame| and the |relativeFrame|. Also make it relative to the window origin.
return mTmpBoundingRect.setIntersect(frame, relativeFrame)
- ? new Rect[]{ new Rect(mTmpBoundingRect) }
+ ? new Rect[]{
+ new Rect(
+ mTmpBoundingRect.left - relativeFrame.left,
+ mTmpBoundingRect.top - relativeFrame.top,
+ mTmpBoundingRect.right - relativeFrame.left,
+ mTmpBoundingRect.bottom - relativeFrame.top
+ )
+ }
: NO_BOUNDING_RECTS;
-
}
// Special treatment for captionBar inset type. During drag-resizing, the |frame| and
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index eff35c0c0..a098e4d 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -4099,7 +4099,7 @@
* whose dataspace has RANGE_EXTENDED.
*
* @param sc The layer whose extended range brightness is being specified
- * @param currentBufferRatio The current hdr/sdr ratio of the current buffer. For example
+ * @param currentBufferRatio The current HDR/SDR ratio of the current buffer. For example
* if the buffer was rendered with a target SDR whitepoint of
* 100 nits and a max display brightness of 200 nits, this should
* be set to 2.0f.
@@ -4113,7 +4113,7 @@
*
* <p>Must be finite && >= 1.0f
*
- * @param desiredRatio The desired hdr/sdr ratio. This can be used to communicate the max
+ * @param desiredRatio The desired HDR/SDR ratio. This can be used to communicate the max
* desired brightness range. This is similar to the "max luminance"
* value in other HDR metadata formats, but represented as a ratio of
* the target SDR whitepoint to the max display brightness. The system
@@ -4150,15 +4150,15 @@
}
/**
- * Sets the desired hdr headroom for the layer.
+ * Sets the desired HDR headroom for the layer.
*
* <p>Prefer using this API over {@link #setExtendedRangeBrightness} for formats that
*. conform to HDR video standards like HLG or HDR10 which do not communicate a HDR/SDR
* ratio as part of generating the buffer.
*
- * @param sc The layer whose desired hdr headroom is being specified
+ * @param sc The layer whose desired HDR headroom is being specified
*
- * @param desiredRatio The desired hdr/sdr ratio. This can be used to communicate the max
+ * @param desiredRatio The desired HDR/SDR ratio. This can be used to communicate the max
* desired brightness range. This is similar to the "max luminance"
* value in other HDR metadata formats, but represented as a ratio of
* the target SDR whitepoint to the max display brightness. The system
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index 124aece..c66abe8 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -889,11 +889,11 @@
* @hide
*/
@Override
- protected int calculateFrameRateCategory(float sizePercentage) {
+ protected int calculateFrameRateCategory(int width, int height) {
if (mMinusTwoFrameIntervalMillis > 15 && mMinusOneFrameIntervalMillis > 15) {
return FRAME_RATE_CATEGORY_NORMAL;
}
- return super.calculateFrameRateCategory(sizePercentage);
+ return super.calculateFrameRateCategory(width, height);
}
@UnsupportedAppUsage
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index a6f380d..a5ff48f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2371,6 +2371,39 @@
*/
protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
+ /**
+ * This indicates that the frame rate category was chosen because it was a small area update.
+ * @hide
+ */
+ public static final int FRAME_RATE_CATEGORY_REASON_SMALL = 0x0100_0000;
+
+ /**
+ * This indicates that the frame rate category was chosen because it was an intermittent update.
+ * @hide
+ */
+ public static final int FRAME_RATE_CATEGORY_REASON_INTERMITTENT = 0x0200_0000;
+
+ /**
+ * This indicates that the frame rate category was chosen because it was a large View.
+ * @hide
+ */
+ public static final int FRAME_RATE_CATEGORY_REASON_LARGE = 0x03000000;
+
+ /**
+ * This indicates that the frame rate category was chosen because it was requested.
+ * @hide
+ */
+ public static final int FRAME_RATE_CATEGORY_REASON_REQUESTED = 0x0400_0000;
+
+ /**
+ * This indicates that the frame rate category was chosen because an invalid frame rate was
+ * requested.
+ * @hide
+ */
+ public static final int FRAME_RATE_CATEGORY_REASON_INVALID = 0x0500_0000;
+
+ private static final int FRAME_RATE_CATEGORY_REASON_MASK = 0xFFFF_0000;
+
private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
// Used to set frame rate compatibility.
@@ -5637,10 +5670,17 @@
private ViewTranslationResponse mViewTranslationResponse;
/**
- * A threshold value to determine the frame rate category of the View based on the size.
+ * Threshold size for something to be considered a small area update (in DP).
+ * This is the dimension for both width and height.
*/
- private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f;
+ private static final float FRAME_RATE_SMALL_SIZE_THRESHOLD = 40f;
+ /**
+ * Threshold size for something to be considered a small area update (in DP) if
+ * it is narrow. This is for either width OR height. For example, a narrow progress
+ * bar could be considered a small area.
+ */
+ private static final float FRAME_RATE_NARROW_THRESHOLD = 10f;
private static final long INFREQUENT_UPDATE_INTERVAL_MILLIS = 100;
private static final int INFREQUENT_UPDATE_COUNTS = 2;
@@ -7017,16 +7057,16 @@
/**
* Clears the request and callback previously set
- * through {@link View#setCredentialManagerRequest}.
+ * through {@link View#setPendingCredentialRequest}.
* Once this API is invoked, there will be no request fired to {@link CredentialManager}
* on future view focus events.
*
- * @see #setCredentialManagerRequest
+ * @see #setPendingCredentialRequest
*/
@FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
- public void clearCredentialManagerRequest() {
+ public void clearPendingCredentialRequest() {
if (Log.isLoggable(AUTOFILL_LOG_TAG, Log.VERBOSE)) {
- Log.v(AUTOFILL_LOG_TAG, "clearCredentialManagerRequest called");
+ Log.v(AUTOFILL_LOG_TAG, "clearPendingCredentialRequest called");
}
mViewCredentialHandler = null;
}
@@ -7056,7 +7096,7 @@
* propagated for the given view
*/
@FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
- public void setCredentialManagerRequest(@NonNull GetCredentialRequest request,
+ public void setPendingCredentialRequest(@NonNull GetCredentialRequest request,
@NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
Preconditions.checkNotNull(request, "request must not be null");
Preconditions.checkNotNull(callback, "request must not be null");
@@ -8582,6 +8622,10 @@
@CallSuper
protected void onFocusChanged(boolean gainFocus, @FocusDirection int direction,
@Nullable Rect previouslyFocusedRect) {
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "onFocusChanged() entered. gainFocus: "
+ + gainFocus);
+ }
if (gainFocus) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
} else {
@@ -8646,6 +8690,9 @@
if (canNotifyAutofillEnterExitEvent()) {
AutofillManager afm = getAutofillManager();
if (afm != null) {
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, this + " afm is not null");
+ }
if (enter) {
// We have not been laid out yet, hence cannot evaluate
// whether this view is visible to the user, we will do
@@ -8657,6 +8704,13 @@
// animation beginning. On the time, the view is not visible
// to the user. And then as the animation progresses, the view
// becomes visible to the user.
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG,
+ "notifyEnterOrExitForAutoFillIfNeeded:"
+ + " isLaidOut(): " + isLaidOut()
+ + " isVisibleToUser(): " + isVisibleToUser()
+ + " isFocused(): " + isFocused());
+ }
if (!isLaidOut() || !isVisibleToUser()) {
mPrivateFlags3 |= PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
} else if (isVisibleToUser()) {
@@ -9550,7 +9604,7 @@
structure.setIsCredential(isCredential());
}
if (getViewCredentialHandler() != null) {
- structure.setCredentialManagerRequest(
+ structure.setPendingCredentialRequest(
getViewCredentialHandler().getRequest(),
getViewCredentialHandler().getCallback());
}
@@ -9955,22 +10009,22 @@
* @hide
*/
public void onGetCredentialResponse(GetCredentialResponse response) {
- if (getCredentialManagerCallback() == null) {
+ if (getPendingCredentialCallback() == null) {
Log.w(AUTOFILL_LOG_TAG, "onGetCredentialResponse called but no callback found");
return;
}
- getCredentialManagerCallback().onResult(response);
+ getPendingCredentialCallback().onResult(response);
}
/**
* @hide
*/
public void onGetCredentialException(String errorType, String errorMsg) {
- if (getCredentialManagerCallback() == null) {
+ if (getPendingCredentialCallback() == null) {
Log.w(AUTOFILL_LOG_TAG, "onGetCredentialException called but no callback found");
return;
}
- getCredentialManagerCallback().onError(new GetCredentialException(errorType, errorMsg));
+ getPendingCredentialCallback().onError(new GetCredentialException(errorType, errorMsg));
}
/**
@@ -10001,13 +10055,13 @@
* the active {@link android.service.autofill.AutofillService} on
* the device.
*
- * <p>See {@link #setCredentialManagerRequest} for more info.
+ * <p>See {@link #setPendingCredentialRequest} for more info.
*
* @return The credential request associated with this View.
*/
@FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
@Nullable
- public final GetCredentialRequest getCredentialManagerRequest() {
+ public final GetCredentialRequest getPendingCredentialRequest() {
if (mViewCredentialHandler == null) {
return null;
}
@@ -10017,14 +10071,14 @@
/**
* Returns the callback that has previously been set up on this view through
- * the {@link #setCredentialManagerRequest} API.
+ * the {@link #setPendingCredentialRequest} API.
* If the return value is null, that means no callback, or request, has been set
* on the view and no {@link CredentialManager} flow will be invoked
* when this view is focused. Traditioanl autofill flows will still
* work, and autofillable content will still be returned through the
* {@link #autofill(AutofillValue)} )} API.
*
- * <p>See {@link #setCredentialManagerRequest} for more info.
+ * <p>See {@link #setPendingCredentialRequest} for more info.
*
* @return The callback associated with this view that will be invoked on a response from
* {@link CredentialManager} .
@@ -10032,7 +10086,7 @@
@FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
@Nullable
public final OutcomeReceiver<GetCredentialResponse,
- GetCredentialException> getCredentialManagerCallback() {
+ GetCredentialException> getPendingCredentialCallback() {
if (mViewCredentialHandler == null) {
return null;
}
@@ -10880,15 +10934,29 @@
}
private boolean isAutofillable() {
- if (getAutofillType() == AUTOFILL_TYPE_NONE) return false;
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "isAutofillable() entered.");
+ }
+ if (getAutofillType() == AUTOFILL_TYPE_NONE) {
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "getAutofillType() returns AUTOFILL_TYPE_NONE");
+ }
+ return false;
+ }
final AutofillManager afm = getAutofillManager();
if (afm == null) {
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "AutofillManager is null");
+ }
return false;
}
// Check whether view is not part of an activity. If it's not, return false.
if (getAutofillViewId() <= LAST_APP_AUTOFILL_ID) {
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "getAutofillViewId()<=LAST_APP_AUTOFILL_ID");
+ }
return false;
}
@@ -10898,11 +10966,18 @@
if ((isImportantForAutofill() && afm.isTriggerFillRequestOnFilteredImportantViewsEnabled())
|| (!isImportantForAutofill()
&& afm.isTriggerFillRequestOnUnimportantViewEnabled())) {
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "isImportantForAutofill(): " + isImportantForAutofill()
+ + "afm.isAutofillable(): " + afm.isAutofillable(this));
+ }
return afm.isAutofillable(this) ? true : notifyAugmentedAutofillIfNeeded(afm);
}
// If the previous condition is not met, fall back to the previous way to trigger fill
// request based on autofill importance instead.
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "isImportantForAutofill(): " + isImportantForAutofill());
+ }
return isImportantForAutofill() ? true : notifyAugmentedAutofillIfNeeded(afm);
}
@@ -10917,6 +10992,11 @@
/** @hide */
public boolean canNotifyAutofillEnterExitEvent() {
+ if (DBG) {
+ Log.d(VIEW_LOG_TAG, "canNotifyAutofillEnterExitEvent() entered. "
+ + " isAutofillable(): " + isAutofillable()
+ + " isAttachedToWindow(): " + isAttachedToWindow());
+ }
return isAutofillable() && isAttachedToWindow();
}
@@ -10962,7 +11042,7 @@
AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId())));
}
if (getViewCredentialHandler() != null) {
- structure.setCredentialManagerRequest(
+ structure.setPendingCredentialRequest(
getViewCredentialHandler().getRequest(),
getViewCredentialHandler().getCallback());
}
@@ -33655,18 +33735,28 @@
*
* @hide
*/
- protected int calculateFrameRateCategory(float sizePercentage) {
+ protected int calculateFrameRateCategory(int width, int height) {
if (mMinusTwoFrameIntervalMillis + mMinusOneFrameIntervalMillis
< INFREQUENT_UPDATE_INTERVAL_MILLIS) {
- if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) {
- return FRAME_RATE_CATEGORY_NORMAL;
+ DisplayMetrics displayMetrics = mResources.getDisplayMetrics();
+ float density = displayMetrics.density;
+ if (density == 0f) {
+ density = 1f;
+ }
+ float widthDp = width / density;
+ float heightDp = height / density;
+ if (widthDp <= FRAME_RATE_NARROW_THRESHOLD
+ || heightDp <= FRAME_RATE_NARROW_THRESHOLD
+ || (widthDp <= FRAME_RATE_SMALL_SIZE_THRESHOLD
+ && heightDp <= FRAME_RATE_SMALL_SIZE_THRESHOLD)) {
+ return FRAME_RATE_CATEGORY_NORMAL | FRAME_RATE_CATEGORY_REASON_SMALL;
} else {
- return FRAME_RATE_CATEGORY_HIGH;
+ return FRAME_RATE_CATEGORY_HIGH | FRAME_RATE_CATEGORY_REASON_LARGE;
}
}
if (mInfrequentUpdateCount == INFREQUENT_UPDATE_COUNTS) {
- return FRAME_RATE_CATEGORY_NORMAL;
+ return FRAME_RATE_CATEGORY_NORMAL | FRAME_RATE_CATEGORY_REASON_INTERMITTENT;
}
return mLastFrameRateCategory;
}
@@ -33674,12 +33764,9 @@
private void votePreferredFrameRate() {
// use toolkitSetFrameRate flag to gate the change
ViewRootImpl viewRootImpl = getViewRootImpl();
- float sizePercentage = getSizePercentage();
- int frameRateCateogry = calculateFrameRateCategory(sizePercentage);
- if (viewRootImpl != null && sizePercentage > 0) {
- if (sToolkitMetricsForFrameRateDecisionFlagValue) {
- viewRootImpl.recordViewPercentage(sizePercentage);
- }
+ int width = mRight - mLeft;
+ int height = mBottom - mTop;
+ if (viewRootImpl != null && (width != 0 && height != 0)) {
if (viewVelocityApi()) {
float velocity = mFrameContentVelocity;
if (velocity < 0f) {
@@ -33691,25 +33778,40 @@
return;
}
}
- if (!Float.isNaN(mPreferredFrameRate)) {
- if (mPreferredFrameRate < 0) {
- if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
- frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE;
- } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
- frameRateCateogry = FRAME_RATE_CATEGORY_LOW;
- } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
- frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL;
- } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
- frameRateCateogry = FRAME_RATE_CATEGORY_HIGH;
- }
+ int frameRateCategory;
+ if (Float.isNaN(mPreferredFrameRate)) {
+ frameRateCategory = calculateFrameRateCategory(width, height);
+ } else if (mPreferredFrameRate < 0) {
+ if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
+ frameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE
+ | FRAME_RATE_CATEGORY_REASON_REQUESTED;
+ } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
+ frameRateCategory = FRAME_RATE_CATEGORY_LOW
+ | FRAME_RATE_CATEGORY_REASON_REQUESTED;
+ } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
+ frameRateCategory = FRAME_RATE_CATEGORY_NORMAL
+ | FRAME_RATE_CATEGORY_REASON_REQUESTED;
+ } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
+ frameRateCategory = FRAME_RATE_CATEGORY_HIGH
+ | FRAME_RATE_CATEGORY_REASON_REQUESTED;
} else {
- viewRootImpl.votePreferredFrameRate(mPreferredFrameRate,
- mFrameRateCompatibility);
- return;
+ // invalid frame rate, default to HIGH
+ frameRateCategory = FRAME_RATE_CATEGORY_HIGH
+ | FRAME_RATE_CATEGORY_REASON_INVALID;
}
+ } else {
+ viewRootImpl.votePreferredFrameRate(mPreferredFrameRate,
+ mFrameRateCompatibility);
+ return;
}
- viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry);
- mLastFrameRateCategory = frameRateCateogry;
+
+ int category = frameRateCategory & ~FRAME_RATE_CATEGORY_REASON_MASK;
+ if (sToolkitMetricsForFrameRateDecisionFlagValue) {
+ int reason = frameRateCategory & FRAME_RATE_CATEGORY_REASON_MASK;
+ viewRootImpl.recordCategory(category, reason, this);
+ }
+ viewRootImpl.votePreferredFrameRateCategory(category);
+ mLastFrameRateCategory = frameRateCategory;
}
}
@@ -33812,7 +33914,7 @@
* - otherwise, use the previous category value.
*/
private void updateInfrequentCount() {
- long currentTimeMillis = AnimationUtils.currentAnimationTimeMillis();
+ long currentTimeMillis = getDrawingTime();
long timeIntervalMillis = currentTimeMillis - mLastUpdateTimeMillis;
mMinusTwoFrameIntervalMillis = mMinusOneFrameIntervalMillis;
mMinusOneFrameIntervalMillis = timeIntervalMillis;
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index fbefbf3..52ff142 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3732,6 +3732,11 @@
return afm.shouldAlwaysIncludeWebviewInAssistStructure();
}
+ private boolean shouldIncludeInvisibleView(AutofillManager afm) {
+ if (afm == null) return false;
+ return afm.shouldIncludeInvisibleViewInAssistStructure();
+ }
+
/** @hide */
private void populateChildrenForAutofill(ArrayList<View> list, @AutofillFlags int flags) {
final int childrenCount = mChildrenCount;
@@ -3754,7 +3759,7 @@
|| (shouldIncludeAllChildrenViewWithAutofillTypeNotNone(afm)
&& child.getAutofillType() != AUTOFILL_TYPE_NONE)
|| shouldIncludeAllChildrenViews(afm)
- || (Flags.includeInvisibleViewGroupInAssistStructure()
+ || (shouldIncludeInvisibleView(afm)
&& child instanceof ViewGroup && child.getVisibility() != View.VISIBLE)) {
// If the child is a ViewGroup object and its visibility is not visible, include
// it as part of the assist structure. The children of these invisible ViewGroup
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 42f6405..6f178bb 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -32,6 +32,11 @@
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_INTERMITTENT;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_INVALID;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_LARGE;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_REQUESTED;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_SMALL;
import static android.view.View.PFLAG_DRAW_ANIMATION;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -59,6 +64,7 @@
import static android.view.ViewRootImplProto.WINDOW_ATTRIBUTES;
import static android.view.ViewRootImplProto.WIN_FRAME;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
+import static android.view.WindowInsetsController.COMPATIBLE_APPEARANCE_FLAGS;
import static android.view.flags.Flags.sensitiveContentAppProtection;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
@@ -841,8 +847,9 @@
private boolean mInsetsAnimationRunning;
private long mPreviousFrameDrawnTime = -1;
- // The largest view size percentage to the display size. Used on trace to collect metric.
- private float mLargestChildPercentage = 0.0f;
+ // The reason the category was changed.
+ private int mFrameRateCategoryChangeReason = 0;
+ private String mFrameRateCategoryView;
/**
* The resolved pointer icon type requested by this window.
@@ -2861,7 +2868,7 @@
final int adjust = inOutParams.softInputMode & SOFT_INPUT_MASK_ADJUST;
if ((inOutParams.privateFlags & PRIVATE_FLAG_APPEARANCE_CONTROLLED) == 0) {
- inOutParams.insetsFlags.appearance = 0;
+ inOutParams.insetsFlags.appearance &= ~COMPATIBLE_APPEARANCE_FLAGS;
if ((sysUiVis & SYSTEM_UI_FLAG_LOW_PROFILE) != 0) {
inOutParams.insetsFlags.appearance |= APPEARANCE_LOW_PROFILE_BARS;
}
@@ -4847,10 +4854,6 @@
long fps = NANOS_PER_SEC / timeDiff;
Trace.setCounter(mFpsTraceName, fps);
mPreviousFrameDrawnTime = expectedDrawnTime;
-
- long percentage = (long) (mLargestChildPercentage * 100);
- Trace.setCounter(mLargestViewTraceName, percentage);
- mLargestChildPercentage = 0.0f;
}
private void reportDrawFinished(@Nullable Transaction t, int seqId) {
@@ -12360,16 +12363,29 @@
// For now, FRAME_RATE_CATEGORY_HIGH_HINT is used for boosting with user interaction.
// FRAME_RATE_CATEGORY_HIGH is for boosting without user interaction
// (e.g., Window Initialization).
- if (mIsFrameRateBoosting || mInsetsAnimationRunning) {
+ if (mIsFrameRateBoosting || mInsetsAnimationRunning
+ || (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE
+ && mPreferredFrameRate > 0)) {
frameRateCategory = FRAME_RATE_CATEGORY_HIGH;
}
try {
if (mLastPreferredFrameRateCategory != frameRateCategory) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ String reason = "none";
+ switch (mFrameRateCategoryChangeReason) {
+ case FRAME_RATE_CATEGORY_REASON_INTERMITTENT -> reason = "intermittent";
+ case FRAME_RATE_CATEGORY_REASON_SMALL -> reason = "small";
+ case FRAME_RATE_CATEGORY_REASON_LARGE -> reason = "large";
+ case FRAME_RATE_CATEGORY_REASON_REQUESTED -> reason = "requested";
+ case FRAME_RATE_CATEGORY_REASON_INVALID -> reason = "invalid frame rate";
+ }
+ String sourceView = mFrameRateCategoryView == null ? "No View Given"
+ : mFrameRateCategoryView;
Trace.traceBegin(
Trace.TRACE_TAG_VIEW, "ViewRootImpl#setFrameRateCategory "
- + frameRateCategory);
+ + frameRateCategory + ", reason " + reason + ", "
+ + sourceView);
}
mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
frameRateCategory, false).applyAsyncUnsafe();
@@ -12383,7 +12399,8 @@
}
private void setPreferredFrameRate(float preferredFrameRate) {
- if (!shouldSetFrameRate()) {
+ if (!shouldSetFrameRate() || (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE
+ && preferredFrameRate > 0)) {
return;
}
@@ -12543,6 +12560,14 @@
}
/**
+ * Get the value of mLastPreferredFrameRate
+ */
+ @VisibleForTesting
+ public float getLastPreferredFrameRate() {
+ return mLastPreferredFrameRate;
+ }
+
+ /**
* Returns whether touch boost is currently enabled.
*/
@VisibleForTesting
@@ -12592,10 +12617,12 @@
mWindowlessBackKeyCallback = callback;
}
- void recordViewPercentage(float percentage) {
+ void recordCategory(int category, int reason, View view) {
if (!Trace.isEnabled()) return;
- // Record the largest view of percentage to the display size.
- mLargestChildPercentage = Math.max(percentage, mLargestChildPercentage);
+ if (category > mPreferredFrameRateCategory) {
+ mFrameRateCategoryChangeReason = reason;
+ mFrameRateCategoryView = view.getClass().getSimpleName();
+ }
}
/**
diff --git a/core/java/android/view/ViewRootInsetsControllerHost.java b/core/java/android/view/ViewRootInsetsControllerHost.java
index f2a3b4c..4214141 100644
--- a/core/java/android/view/ViewRootInsetsControllerHost.java
+++ b/core/java/android/view/ViewRootInsetsControllerHost.java
@@ -17,6 +17,7 @@
package android.view;
import static android.view.InsetsController.DEBUG;
+import static android.view.WindowInsetsController.COMPATIBLE_APPEARANCE_FLAGS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_APPEARANCE_CONTROLLED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
@@ -173,7 +174,9 @@
@Override
public void setSystemBarsAppearance(int appearance, int mask) {
- mViewRoot.mWindowAttributes.privateFlags |= PRIVATE_FLAG_APPEARANCE_CONTROLLED;
+ if ((mask & COMPATIBLE_APPEARANCE_FLAGS) != 0) {
+ mViewRoot.mWindowAttributes.privateFlags |= PRIVATE_FLAG_APPEARANCE_CONTROLLED;
+ }
final InsetsFlags insetsFlags = mViewRoot.mWindowAttributes.insetsFlags;
final int newAppearance = (insetsFlags.appearance & ~mask) | (appearance & mask);
if (insetsFlags.appearance != newAppearance) {
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 1efd375..6c852c3 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -361,11 +361,11 @@
* {@link android.credentials.CredentialManager} request will be fired when this
* node is focused.
* <p> For details on how a request and callback can be set, see
- * {@link ViewStructure#setCredentialManagerRequest(GetCredentialRequest, OutcomeReceiver)}
+ * {@link ViewStructure#setPendingCredentialRequest(GetCredentialRequest, OutcomeReceiver)}
*/
@Nullable
@FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
- public GetCredentialRequest getCredentialManagerRequest() {
+ public GetCredentialRequest getPendingCredentialRequest() {
return null;
}
@@ -376,12 +376,12 @@
* {@link android.credentials.CredentialManager} request will be fired when this
* node is focused.
* <p> For details on how a request and callback can be set, see
- * {@link ViewStructure#setCredentialManagerRequest(GetCredentialRequest, OutcomeReceiver)}
+ * {@link ViewStructure#setPendingCredentialRequest(GetCredentialRequest, OutcomeReceiver)}
*/
@Nullable
@FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
public OutcomeReceiver<
- GetCredentialResponse, GetCredentialException> getCredentialManagerCallback() {
+ GetCredentialResponse, GetCredentialException> getPendingCredentialCallback() {
return null;
}
@@ -555,12 +555,12 @@
* @param callback the callback where the response or exception, is returned
*/
@FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
- public void setCredentialManagerRequest(@NonNull GetCredentialRequest request,
+ public void setPendingCredentialRequest(@NonNull GetCredentialRequest request,
@NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {}
/**
* Clears the credential request previously set through
- * {@link ViewStructure#setCredentialManagerRequest(GetCredentialRequest, OutcomeReceiver)}
+ * {@link ViewStructure#setPendingCredentialRequest(GetCredentialRequest, OutcomeReceiver)}
*/
@FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
public void clearCredentialManagerRequest() {}
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 6b427fc..51229a7 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -666,7 +666,7 @@
* Update the status bar appearance.
*/
- void updateStatusBarAppearance(int appearance);
+ void updateSystemBarsAppearance(int appearance);
/**
* Update the navigation bar color to a forced one.
@@ -1038,6 +1038,11 @@
}
/** @hide */
+ public final void setSystemBarAppearance(@WindowInsetsController.Appearance int appearance) {
+ mSystemBarAppearance = appearance;
+ }
+
+ /** @hide */
@WindowInsetsController.Appearance
public final int getSystemBarAppearance() {
return mSystemBarAppearance;
@@ -1046,12 +1051,12 @@
/** @hide */
public final void dispatchOnSystemBarAppearanceChanged(
@WindowInsetsController.Appearance int appearance) {
- mSystemBarAppearance = appearance;
+ setSystemBarAppearance(appearance);
if (mDecorCallback != null) {
mDecorCallback.onSystemBarAppearanceChanged(appearance);
}
if (mWindowControllerCallback != null) {
- mWindowControllerCallback.updateStatusBarAppearance(appearance);
+ mWindowControllerCallback.updateSystemBarsAppearance(appearance);
}
}
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index b7542dc..7601ffa 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -94,15 +94,36 @@
int APPEARANCE_LIGHT_CAPTION_BARS = 1 << 8;
/**
+ * Same as {@link #APPEARANCE_LIGHT_NAVIGATION_BARS} but set by the system. The system will
+ * respect {@link #APPEARANCE_LIGHT_NAVIGATION_BARS} when this is cleared.
+ * @hide
+ */
+ int APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS = 1 << 9;
+
+ /**
+ * Appearance flags that can be implied from system UI flags.
+ * @hide
+ */
+ int COMPATIBLE_APPEARANCE_FLAGS = APPEARANCE_LOW_PROFILE_BARS
+ | APPEARANCE_LIGHT_STATUS_BARS
+ | APPEARANCE_LIGHT_NAVIGATION_BARS;
+
+ /**
* Determines the appearance of system bars.
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
- @IntDef(flag = true, value = {APPEARANCE_OPAQUE_STATUS_BARS, APPEARANCE_OPAQUE_NAVIGATION_BARS,
- APPEARANCE_LOW_PROFILE_BARS, APPEARANCE_LIGHT_STATUS_BARS,
- APPEARANCE_LIGHT_NAVIGATION_BARS, APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS,
+ @IntDef(flag = true, value = {
+ APPEARANCE_OPAQUE_STATUS_BARS,
+ APPEARANCE_OPAQUE_NAVIGATION_BARS,
+ APPEARANCE_LOW_PROFILE_BARS,
+ APPEARANCE_LIGHT_STATUS_BARS,
+ APPEARANCE_LIGHT_NAVIGATION_BARS,
+ APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS,
APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS,
- APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND, APPEARANCE_LIGHT_CAPTION_BARS})
+ APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND,
+ APPEARANCE_LIGHT_CAPTION_BARS,
+ APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS})
@interface Appearance {
}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index ae4a7d3..9708591 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -48,6 +48,7 @@
import android.content.res.Resources;
import android.os.Binder;
import android.os.Build;
+import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.IBinder;
@@ -67,6 +68,8 @@
import android.view.accessibility.AccessibilityEvent.EventType;
import com.android.internal.R;
+import com.android.internal.accessibility.common.ShortcutConstants;
+import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IntPair;
@@ -78,6 +81,8 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.concurrent.Executor;
/**
@@ -189,11 +194,13 @@
* <p>Note: Keep in sync with {@link #SHORTCUT_TYPES}.</p>
* @hide
*/
+ // TODO(b/323686675): reuse the one defined in ShortcutConstants
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
// LINT.IfChange(shortcut_type_intdef)
ACCESSIBILITY_BUTTON,
- ACCESSIBILITY_SHORTCUT_KEY
+ ACCESSIBILITY_SHORTCUT_KEY,
+ UserShortcutType.QUICK_SETTINGS,
// LINT.ThenChange(:shortcut_type_array)
})
public @interface ShortcutType {}
@@ -207,6 +214,7 @@
// LINT.IfChange(shortcut_type_array)
ACCESSIBILITY_BUTTON,
ACCESSIBILITY_SHORTCUT_KEY,
+ UserShortcutType.QUICK_SETTINGS,
// LINT.ThenChange(:shortcut_type_intdef)
};
@@ -1631,6 +1639,74 @@
}
/**
+ * Turns on or off a shortcut type of the accessibility features. The shortcut type is one
+ * of the shortcut defined in the {@link ShortcutConstants.USER_SHORTCUT_TYPES}.
+ *
+ * @throws SecurityException if the app does not hold the
+ * {@link Manifest.permission#MANAGE_ACCESSIBILITY} permission
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+ public void enableShortcutsForTargets(boolean enable,
+ @UserShortcutType int shortcutTypes, @NonNull Set<String> targets,
+ @UserIdInt int userId) {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ }
+ try {
+ service.enableShortcutsForTargets(
+ enable, shortcutTypes, targets.stream().toList(), userId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns accessibility feature's component and the provided tile map. This includes the
+ * TileService provided by the AccessibilityService or Accessibility Activity and the tile
+ * component provided by the framework's feature.
+ *
+ * @return a map of a feature's component name, and its provided tile's component name. The
+ * returned map's keys and values are not null. If a feature doesn't provide a tile, it won't
+ * have an entry in this map.
+ * @hide
+ * @see ShortcutConstants.A11Y_FEATURE_TO_FRAMEWORK_TILE
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+ @NonNull
+ public Map<ComponentName, ComponentName> getA11yFeatureToTileMap(@UserIdInt int userId) {
+ final IAccessibilityManager service;
+ Map<ComponentName, ComponentName> a11yFeatureToTileMap = new ArrayMap<>();
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return a11yFeatureToTileMap;
+ }
+ }
+ try {
+ Bundle a11yFeatureToTile = service.getA11yFeatureToTileMap(userId);
+ for (String key : a11yFeatureToTile.keySet()) {
+ ComponentName feature = ComponentName.unflattenFromString(key);
+ if (feature == null) {
+ continue;
+ }
+ ComponentName tileService = a11yFeatureToTile.getParcelable(key,
+ ComponentName.class);
+ if (tileService != null) {
+ a11yFeatureToTileMap.put(feature, tileService);
+ }
+ }
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ return a11yFeatureToTileMap;
+ }
+
+ /**
* Register the provided {@link RemoteAction} with the given actionId
* <p>
* To perform established system actions, an accessibility service uses the GLOBAL_ACTION
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 9617606..e215950 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -22,6 +22,7 @@
import android.accessibilityservice.IAccessibilityServiceClient;
import android.content.ComponentName;
import android.content.pm.ParceledListSlice;
+import android.os.Bundle;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
@@ -143,4 +144,10 @@
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE)")
oneway void notifyQuickSettingsTilesChanged(int userId, in List<ComponentName> tileComponentNames);
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
+ oneway void enableShortcutsForTargets(boolean enable, int shortcutTypes, in List<String> shortcutTargets, int userId);
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
+ Bundle getA11yFeatureToTileMap(int userId);
}
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index 334c2b77..644a7a9 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -220,6 +220,41 @@
DEVICE_CONFIG_ALWAYS_INCLUDE_WEBVIEW_IN_ASSIST_STRUCTURE =
"always_include_webview_in_assist_structure";
+ /**
+ * Whether to include invisible views in the assist structure. Including invisible views can fix
+ * some cases in which Session is destroyed earlier than it is suppose to.
+ *
+ * <p>See
+ * frameworks/base/services/autofill/bugfixes.aconfig#include_invisible_view_group_in_assist_structure
+ * for more information.
+ *
+ * @hide
+ */
+ public static final String DEVICE_CONFIG_INCLUDE_INVISIBLE_VIEW_GROUP_IN_ASSIST_STRUCTURE =
+ "include_invisible_view_group_in_assist_structure";
+
+ /**
+ * Bugfix flag, Autofill should ignore views resetting to empty states.
+ *
+ * See frameworks/base/services/autofill/bugfixes.aconfig#ignore_view_state_reset_to_empty
+ * for more information.
+ *
+ * @hide
+ */
+ public static final String DEVICE_CONFIG_IGNORE_VIEW_STATE_RESET_TO_EMPTY =
+ "ignore_view_state_reset_to_empty";
+
+ /**
+ * Bugfix flag, Autofill should ignore view updates if an Auth intent is showing.
+ *
+ * See frameworks/base/services/autofill/bugfixes.aconfig#relayout
+ * for more information.
+ *
+ * @hide
+ */
+ public static final String DEVICE_CONFIG_IGNORE_RELAYOUT_WHEN_AUTH_PENDING =
+ "ignore_relayout_auth_pending";
+
// END AUTOFILL FOR ALL APPS FLAGS //
@@ -473,6 +508,29 @@
DEVICE_CONFIG_ALWAYS_INCLUDE_WEBVIEW_IN_ASSIST_STRUCTURE, true);
}
+ /** @hide */
+ public static boolean shouldIncludeInvisibleViewInAssistStructure() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_AUTOFILL,
+ DEVICE_CONFIG_INCLUDE_INVISIBLE_VIEW_GROUP_IN_ASSIST_STRUCTURE,
+ false);
+ }
+
+ /** @hide */
+ public static boolean shouldIgnoreViewStateResetToEmpty() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_AUTOFILL,
+ DEVICE_CONFIG_IGNORE_VIEW_STATE_RESET_TO_EMPTY,
+ false);
+ }
+
+ /** @hide */
+ public static boolean shouldIgnoreRelayoutWhenAuthPending() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_AUTOFILL,
+ DEVICE_CONFIG_IGNORE_RELAYOUT_WHEN_AUTH_PENDING,
+ false);
+ }
/**
* Whether should enable multi-line filter
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index cf6b9e5..e15baae 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -739,6 +739,9 @@
// Indicate whether WebView should always be included in the assist structure
private boolean mShouldAlwaysIncludeWebviewInAssistStructure;
+ // Indicate whether invisibles views should be included in the assist structure
+ private boolean mShouldIncludeInvisibleViewInAssistStructure;
+
// Controls logic around apps changing some properties of their views when activity loses
// focus due to autofill showing biometric activity, password manager, or password breach check.
private boolean mRelayoutFix;
@@ -968,7 +971,10 @@
mShouldAlwaysIncludeWebviewInAssistStructure =
AutofillFeatureFlags.shouldAlwaysIncludeWebviewInAssistStructure();
- mRelayoutFix = Flags.relayout();
+ mShouldIncludeInvisibleViewInAssistStructure =
+ AutofillFeatureFlags.shouldIncludeInvisibleViewInAssistStructure();
+
+ mRelayoutFix = AutofillFeatureFlags.shouldIgnoreRelayoutWhenAuthPending();
mIsCredmanIntegrationEnabled = Flags.autofillCredmanIntegration();
}
@@ -1055,6 +1061,13 @@
}
/**
+ * @hide
+ */
+ public boolean shouldIncludeInvisibleViewInAssistStructure() {
+ return mShouldIncludeInvisibleViewInAssistStructure;
+ }
+
+ /**
* Get the denied or allowed activitiy names under specified package from the list string and
* set it in fields accordingly
*
@@ -1480,20 +1493,29 @@
if (infos.size() == 0) {
throw new IllegalArgumentException("No VirtualViewInfo found");
}
+ boolean isCredmanRequested = false;
if (shouldSuppressDialogsForCredman(view)
&& mIsFillAndSaveDialogDisabledForCredentialManager) {
- if (sDebug) {
- Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:"
- + view.getAutofillId().toString());
- }
mScreenHasCredmanField = true;
- return;
+ if (isCredmanRequested(view)) {
+ if (sDebug) {
+ Log.d(TAG, "Prefetching fill response for credMan: "
+ + view.getAutofillId().toString());
+ }
+ isCredmanRequested = true;
+ } else {
+ if (sDebug) {
+ Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:"
+ + view.getAutofillId().toString());
+ }
+ return;
+ }
}
for (int i = 0; i < infos.size(); i++) {
final VirtualViewFillInfo info = infos.valueAt(i);
final int virtualId = infos.keyAt(i);
notifyViewReadyInner(getAutofillId(view, virtualId),
- (info == null) ? null : info.getAutofillHints());
+ (info == null) ? null : info.getAutofillHints(), isCredmanRequested);
}
}
@@ -1505,19 +1527,29 @@
* @hide
*/
public void notifyViewEnteredForFillDialog(View v) {
+ boolean isCredmanRequested = false;
if (shouldSuppressDialogsForCredman(v)
&& mIsFillAndSaveDialogDisabledForCredentialManager) {
- if (sDebug) {
- Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:"
- + v.getAutofillId());
- }
mScreenHasCredmanField = true;
- return;
+ if (isCredmanRequested(v)) {
+ if (sDebug) {
+ Log.d(TAG, "Prefetching fill response for credMan: "
+ + v.getAutofillId().toString());
+ }
+ isCredmanRequested = true;
+ } else {
+ if (sDebug) {
+ Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:"
+ + v.getAutofillId().toString());
+ }
+ return;
+ }
}
- notifyViewReadyInner(v.getAutofillId(), v.getAutofillHints());
+ notifyViewReadyInner(v.getAutofillId(), v.getAutofillHints(), isCredmanRequested);
}
- private void notifyViewReadyInner(AutofillId id, @Nullable String[] autofillHints) {
+ private void notifyViewReadyInner(AutofillId id, @Nullable String[] autofillHints,
+ boolean isCredmanRequested) {
if (sDebug) {
Log.d(TAG, "notifyViewReadyInner:" + id);
}
@@ -1592,6 +1624,12 @@
}
int flags = FLAG_SUPPORTS_FILL_DIALOG;
flags |= FLAG_VIEW_NOT_FOCUSED;
+ if (isCredmanRequested) {
+ if (sDebug) {
+ Log.d(TAG, "Pre fill request is triggered for credMan");
+ }
+ flags |= FLAG_VIEW_REQUESTS_CREDMAN_SERVICE;
+ }
synchronized (mLock) {
// To match the id of the IME served view, used AutofillId.NO_AUTOFILL_ID on prefill
// request, because IME will reset the id of IME served view to 0 when activity
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 16fecc1..7885cd9 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -499,6 +499,25 @@
@TestApi
public InputMethodInfo(@NonNull String packageName, @NonNull String className,
@NonNull CharSequence label, @NonNull String settingsActivity,
+ boolean supportStylusHandwriting,
+ @NonNull String stylusHandwritingSettingsActivityAttr) {
+ this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */,
+ settingsActivity, null /* languageSettingsActivity */,
+ null /* subtypes */, 0 /* isDefaultResId */,
+ false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */,
+ false /* inlineSuggestionsEnabled */, false /* isVrOnly */,
+ false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */,
+ supportStylusHandwriting, false /* supportConnectionlessStylusHandwriting */,
+ stylusHandwritingSettingsActivityAttr, false /* inlineSuggestionsEnabled */);
+ }
+
+ /**
+ * Test API for creating a built-in input method to verify stylus handwriting.
+ * @hide
+ */
+ @TestApi
+ public InputMethodInfo(@NonNull String packageName, @NonNull String className,
+ @NonNull CharSequence label, @NonNull String settingsActivity,
@NonNull String languageSettingsActivity, boolean supportStylusHandwriting,
@NonNull String stylusHandwritingSettingsActivityAttr) {
this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */,
@@ -881,6 +900,8 @@
+ Integer.toHexString(mIsDefaultResId));
pw.println(prefix + "Service:");
mService.dump(pw, prefix + " ");
+ pw.println(prefix + "InputMethodSubtype array: count=" + mSubtypes.getCount());
+ mSubtypes.dump(pw, prefix + " ");
}
@Override
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index b0b9460..be91cfb 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -28,6 +28,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import android.util.Printer;
import android.util.Slog;
import com.android.internal.inputmethod.SubtypeLocaleUtils;
@@ -791,6 +792,20 @@
dest.writeInt(mIsAsciiCapable ? 1 : 0);
}
+ void dump(@NonNull Printer pw, @NonNull String prefix) {
+ pw.println(prefix + "mSubtypeNameOverride=" + mSubtypeNameOverride
+ + " mPkLanguageTag=" + mPkLanguageTag
+ + " mPkLayoutType=" + mPkLayoutType
+ + " mSubtypeId=" + mSubtypeId
+ + " mSubtypeLocale=" + mSubtypeLocale
+ + " mSubtypeLanguageTag=" + mSubtypeLanguageTag
+ + " mSubtypeMode=" + mSubtypeMode
+ + " mIsAuxiliary=" + mIsAuxiliary
+ + " mOverridesImplicitlyEnabledSubtype=" + mOverridesImplicitlyEnabledSubtype
+ + " mIsAsciiCapable=" + mIsAsciiCapable
+ + " mSubtypeHashCode=" + mSubtypeHashCode);
+ }
+
public static final @android.annotation.NonNull Parcelable.Creator<InputMethodSubtype> CREATOR
= new Parcelable.Creator<InputMethodSubtype>() {
@Override
diff --git a/core/java/android/view/inputmethod/InputMethodSubtypeArray.java b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java
index ee36dc7..c243a22 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtypeArray.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java
@@ -16,9 +16,11 @@
package android.view.inputmethod;
+import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.BadParcelableException;
import android.os.Parcel;
+import android.util.Printer;
import android.util.Slog;
import java.io.ByteArrayInputStream;
@@ -174,6 +176,19 @@
private volatile byte[] mCompressedData;
private volatile int mDecompressedSize;
+ void dump(@NonNull Printer pw, @NonNull String prefix) {
+ final var innerPrefix = prefix + " ";
+ for (int i = 0; i < mCount; i++) {
+ pw.println(prefix + "InputMethodSubtype #" + i + ":");
+ final var subtype = get(i);
+ if (subtype != null) {
+ subtype.dump(pw, innerPrefix);
+ } else {
+ pw.println(innerPrefix + "missing subtype");
+ }
+ }
+ }
+
private static byte[] marshall(final InputMethodSubtype[] array) {
Parcel parcel = null;
try {
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 14c5348..d12eda3 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1203,7 +1203,11 @@
* changes to this setting after that point.
*
* @param flag {@code true} if the WebView should use the database storage API
+ * @deprecated WebSQL is deprecated and this method will become a no-op on all
+ * Android versions once support is removed in Chromium. See
+ * https://developer.chrome.com/blog/deprecating-web-sql for more information.
*/
+ @Deprecated
public abstract void setDatabaseEnabled(boolean flag);
/**
@@ -1236,7 +1240,11 @@
*
* @return {@code true} if the database storage API is enabled
* @see #setDatabaseEnabled
+ * @deprecated WebSQL is deprecated and this method will become a no-op on all
+ * Android versions once support is removed in Chromium. See
+ * https://developer.chrome.com/blog/deprecating-web-sql for more information.
*/
+ @Deprecated
public abstract boolean getDatabaseEnabled();
/**
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 139ebc3..d40eeda 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2112,6 +2112,17 @@
}
}
+ boolean shouldDrawHighlightsOnTop = highContrastTextSmallTextRect()
+ && canvas.isHighContrastTextEnabled();
+
+ // If high contrast text is drawing background rectangles behind the text, those cover up
+ // the cursor and correction highlighter etc. So just draw the text first, then draw the
+ // others on top of the text. If high contrast text isn't enabled: draw text last, as usual.
+ if (shouldDrawHighlightsOnTop) {
+ drawLayout(canvas, layout, highlightPaths, highlightPaints, selectionHighlight,
+ selectionHighlightPaint, cursorOffsetVertical, shouldDrawHighlightsOnTop);
+ }
+
if (mCorrectionHighlighter != null) {
mCorrectionHighlighter.draw(canvas, cursorOffsetVertical);
}
@@ -2136,9 +2147,19 @@
mInsertModeController.onDraw(canvas);
}
+ if (!shouldDrawHighlightsOnTop) {
+ drawLayout(canvas, layout, highlightPaths, highlightPaints, selectionHighlight,
+ selectionHighlightPaint, cursorOffsetVertical, shouldDrawHighlightsOnTop);
+ }
+ }
+
+ private void drawLayout(Canvas canvas, Layout layout, List<Path> highlightPaths,
+ List<Paint> highlightPaints, Path selectionHighlight, Paint selectionHighlightPaint,
+ int cursorOffsetVertical, boolean shouldDrawHighlightsOnTop) {
if (mTextView.canHaveDisplayList() && canvas.isHardwareAccelerated()) {
drawHardwareAccelerated(canvas, layout, highlightPaths, highlightPaints,
- selectionHighlight, selectionHighlightPaint, cursorOffsetVertical);
+ selectionHighlight, selectionHighlightPaint, cursorOffsetVertical,
+ shouldDrawHighlightsOnTop);
} else {
layout.draw(canvas, highlightPaths, highlightPaints, selectionHighlight,
selectionHighlightPaint, cursorOffsetVertical);
@@ -2147,14 +2168,13 @@
private void drawHardwareAccelerated(Canvas canvas, Layout layout,
List<Path> highlightPaths, List<Paint> highlightPaints,
- Path selectionHighlight, Paint selectionHighlightPaint, int cursorOffsetVertical) {
+ Path selectionHighlight, Paint selectionHighlightPaint, int cursorOffsetVertical,
+ boolean shouldDrawHighlightsOnTop) {
final long lineRange = layout.getLineRangeForDraw(canvas);
int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
if (lastLine < 0) return;
- boolean shouldDrawHighlightsOnTop = highContrastTextSmallTextRect()
- && canvas.isHighContrastTextEnabled();
if (!shouldDrawHighlightsOnTop) {
layout.drawWithoutText(canvas, highlightPaths, highlightPaints, selectionHighlight,
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index a2d8d80..e3caf70 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -5127,7 +5127,10 @@
* @param viewId The id of the {@link AdapterView}
* @param intent The intent of the service which will be
* providing data to the RemoteViewsAdapter
+ * @deprecated use
+ * {@link #setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)} instead
*/
+ @Deprecated
public void setRemoteAdapter(@IdRes int viewId, Intent intent) {
if (remoteAdapterConversion()) {
addAction(new SetRemoteCollectionItemListAdapterAction(viewId, intent));
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 1721462..0373539 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -15490,8 +15490,9 @@
return x;
}
+ /** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- int getLineAtCoordinate(float y) {
+ public int getLineAtCoordinate(float y) {
y -= getTotalPaddingTop();
// Clamp the position to inside of the view.
y = Math.max(0.0f, y);
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
index c76c7a4..e5658e6 100644
--- a/core/java/android/window/SnapshotDrawerUtils.java
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -36,6 +36,7 @@
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
@@ -424,9 +425,12 @@
layoutParams.flags = (windowFlags & ~FLAG_INHERIT_EXCLUDES)
| FLAG_NOT_FOCUSABLE
| FLAG_NOT_TOUCHABLE;
- // Setting as trusted overlay to let touches pass through. This is safe because this
- // window is controlled by the system.
- layoutParams.privateFlags = (windowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS)
+ layoutParams.privateFlags =
+ (windowPrivateFlags
+ & (PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS
+ | PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED))
+ // Setting as trusted overlay to let touches pass through. This is safe because this
+ // window is controlled by the system.
| PRIVATE_FLAG_TRUSTED_OVERLAY;
layoutParams.token = token;
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
@@ -475,14 +479,16 @@
mStatusBarColor = DecorView.calculateBarColor(windowFlags, FLAG_TRANSLUCENT_STATUS,
semiTransparent, taskDescription.getStatusBarColor(), appearance,
APPEARANCE_LIGHT_STATUS_BARS,
- taskDescription.getEnsureStatusBarContrastWhenTransparent());
+ taskDescription.getEnsureStatusBarContrastWhenTransparent(),
+ false /* movesBarColorToScrim */);
mNavigationBarColor = DecorView.calculateBarColor(windowFlags,
FLAG_TRANSLUCENT_NAVIGATION, semiTransparent,
taskDescription.getNavigationBarColor(), appearance,
APPEARANCE_LIGHT_NAVIGATION_BARS,
taskDescription.getEnsureNavigationBarContrastWhenTransparent()
&& context.getResources().getBoolean(
- R.bool.config_navBarNeedsScrim));
+ R.bool.config_navBarNeedsScrim),
+ (windowPrivateFlags & PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED) != 0);
mStatusBarPaint.setColor(mStatusBarColor);
mNavigationBarPaint.setColor(mNavigationBarColor);
mRequestedVisibleTypes = requestedVisibleTypes;
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
index 7b8cdff..7e77f15 100644
--- a/core/java/android/window/TaskFragmentOperation.java
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -24,6 +24,7 @@
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.view.SurfaceControl;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -111,7 +112,8 @@
/**
* Creates a decor surface in the parent Task of the TaskFragment. The created decor surface
* will be provided in {@link TaskFragmentTransaction#TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED}
- * event callback.
+ * event callback. The decor surface can be used to draw the divider between TaskFragments or
+ * other decorations.
*/
public static final int OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE = 14;
@@ -135,6 +137,15 @@
*/
public static final int OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH = 17;
+ /**
+ * Sets whether the decor surface will be boosted. When not boosted, the decor surface is placed
+ * below any TaskFragments in untrusted mode or any activities with uid different from the
+ * TaskFragmentOrganizer uid and just above its owner TaskFragment; when boosted, the decor
+ * surface is placed above all the non-boosted windows in the Task, the content of these
+ * non-boosted windows will be hidden and inputs are disabled.
+ */
+ public static final int OP_TYPE_SET_DECOR_SURFACE_BOOSTED = 18;
+
@IntDef(prefix = { "OP_TYPE_" }, value = {
OP_TYPE_UNKNOWN,
OP_TYPE_CREATE_TASK_FRAGMENT,
@@ -155,6 +166,7 @@
OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE,
OP_TYPE_SET_DIM_ON_TASK,
OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH,
+ OP_TYPE_SET_DECOR_SURFACE_BOOSTED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface OperationType {}
@@ -186,12 +198,18 @@
private final boolean mMoveToBottomIfClearWhenLaunch;
+ private final boolean mBooleanValue;
+
+ @Nullable
+ private final SurfaceControl.Transaction mSurfaceTransaction;
+
private TaskFragmentOperation(@OperationType int opType,
@Nullable TaskFragmentCreationParams taskFragmentCreationParams,
@Nullable IBinder activityToken, @Nullable Intent activityIntent,
@Nullable Bundle bundle, @Nullable IBinder secondaryFragmentToken,
@Nullable TaskFragmentAnimationParams animationParams,
- boolean isolatedNav, boolean dimOnTask, boolean moveToBottomIfClearWhenLaunch) {
+ boolean isolatedNav, boolean dimOnTask, boolean moveToBottomIfClearWhenLaunch,
+ boolean booleanValue, @Nullable SurfaceControl.Transaction surfaceTransaction) {
mOpType = opType;
mTaskFragmentCreationParams = taskFragmentCreationParams;
mActivityToken = activityToken;
@@ -202,6 +220,8 @@
mIsolatedNav = isolatedNav;
mDimOnTask = dimOnTask;
mMoveToBottomIfClearWhenLaunch = moveToBottomIfClearWhenLaunch;
+ mBooleanValue = booleanValue;
+ mSurfaceTransaction = surfaceTransaction;
}
private TaskFragmentOperation(Parcel in) {
@@ -215,6 +235,8 @@
mIsolatedNav = in.readBoolean();
mDimOnTask = in.readBoolean();
mMoveToBottomIfClearWhenLaunch = in.readBoolean();
+ mBooleanValue = in.readBoolean();
+ mSurfaceTransaction = in.readTypedObject(SurfaceControl.Transaction.CREATOR);
}
@Override
@@ -229,6 +251,8 @@
dest.writeBoolean(mIsolatedNav);
dest.writeBoolean(mDimOnTask);
dest.writeBoolean(mMoveToBottomIfClearWhenLaunch);
+ dest.writeBoolean(mBooleanValue);
+ dest.writeTypedObject(mSurfaceTransaction, flags);
}
@NonNull
@@ -324,6 +348,22 @@
return mMoveToBottomIfClearWhenLaunch;
}
+ /** Returns the boolean value for this operation. */
+ public boolean getBooleanValue() {
+ return mBooleanValue;
+ }
+
+ /**
+ * Returns {@link SurfaceControl.Transaction} associated with this operation. Currently, this is
+ * only used by {@link TaskFragmentOperation#OP_TYPE_SET_DECOR_SURFACE_BOOSTED} to specify a
+ * {@link SurfaceControl.Transaction} that should be applied together with the transaction to
+ * change the decor surface layers.
+ */
+ @Nullable
+ public SurfaceControl.Transaction getSurfaceTransaction() {
+ return mSurfaceTransaction;
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
@@ -349,6 +389,10 @@
sb.append(", isolatedNav=").append(mIsolatedNav);
sb.append(", dimOnTask=").append(mDimOnTask);
sb.append(", moveToBottomIfClearWhenLaunch=").append(mMoveToBottomIfClearWhenLaunch);
+ sb.append(", booleanValue=").append(mBooleanValue);
+ if (mSurfaceTransaction != null) {
+ sb.append(", surfaceTransaction=").append(mSurfaceTransaction);
+ }
sb.append('}');
return sb.toString();
@@ -358,7 +402,7 @@
public int hashCode() {
return Objects.hash(mOpType, mTaskFragmentCreationParams, mActivityToken, mActivityIntent,
mBundle, mSecondaryFragmentToken, mAnimationParams, mIsolatedNav, mDimOnTask,
- mMoveToBottomIfClearWhenLaunch);
+ mMoveToBottomIfClearWhenLaunch, mBooleanValue, mSurfaceTransaction);
}
@Override
@@ -376,7 +420,9 @@
&& Objects.equals(mAnimationParams, other.mAnimationParams)
&& mIsolatedNav == other.mIsolatedNav
&& mDimOnTask == other.mDimOnTask
- && mMoveToBottomIfClearWhenLaunch == other.mMoveToBottomIfClearWhenLaunch;
+ && mMoveToBottomIfClearWhenLaunch == other.mMoveToBottomIfClearWhenLaunch
+ && mBooleanValue == other.mBooleanValue
+ && Objects.equals(mSurfaceTransaction, other.mSurfaceTransaction);
}
@Override
@@ -414,6 +460,11 @@
private boolean mMoveToBottomIfClearWhenLaunch;
+ private boolean mBooleanValue;
+
+ @Nullable
+ private SurfaceControl.Transaction mSurfaceTransaction;
+
/**
* @param opType the {@link OperationType} of this {@link TaskFragmentOperation}.
*/
@@ -505,13 +556,37 @@
}
/**
+ * Sets the boolean value for this operation.
+ * TODO(b/327338038) migrate other boolean values to use shared mBooleanValue
+ */
+ @NonNull
+ public Builder setBooleanValue(boolean booleanValue) {
+ mBooleanValue = booleanValue;
+ return this;
+ }
+
+ /**
+ * Sets {@link SurfaceControl.Transaction} associated with this operation. Currently, this
+ * is only used by {@link TaskFragmentOperation#OP_TYPE_SET_DECOR_SURFACE_BOOSTED} to
+ * specify a {@link SurfaceControl.Transaction} that should be applied together with the
+ * transaction to change the decor surface layers.
+ */
+ @NonNull
+ public Builder setSurfaceTransaction(
+ @Nullable SurfaceControl.Transaction surfaceTransaction) {
+ mSurfaceTransaction = surfaceTransaction;
+ return this;
+ }
+
+ /**
* Constructs the {@link TaskFragmentOperation}.
*/
@NonNull
public TaskFragmentOperation build() {
return new TaskFragmentOperation(mOpType, mTaskFragmentCreationParams, mActivityToken,
mActivityIntent, mBundle, mSecondaryFragmentToken, mAnimationParams,
- mIsolatedNav, mDimOnTask, mMoveToBottomIfClearWhenLaunch);
+ mIsolatedNav, mDimOnTask, mMoveToBottomIfClearWhenLaunch, mBooleanValue,
+ mSurfaceTransaction);
}
}
}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 15b9b78..3685bba 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -1021,6 +1021,10 @@
sb.append(" component=");
sb.append(mActivityComponent.flattenToShortString());
}
+ if (mTaskInfo != null) {
+ sb.append(" taskParent=");
+ sb.append(mTaskInfo.parentTaskId);
+ }
sb.append('}');
return sb.toString();
}
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 63a2474..ed1d434 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -14,3 +14,10 @@
description: "Enables desktop windowing"
bug: "304778354"
}
+
+flag {
+ name: "enable_desktop_windowing_modals_policy"
+ namespace: "lse_desktop_experience"
+ description: "Enables policy for modals activities"
+ bug: "319492844"
+}
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index ce74848..82e613e 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -77,4 +77,12 @@
description: "Properties to allow apps and activities to opt-in to cover display rendering"
bug: "312530526"
is_fixed_read_only: true
+}
+
+flag {
+ namespace: "windowing_sdk"
+ name: "enable_wm_extensions_for_all_flag"
+ description: "Whether to enable WM Extensions for all devices"
+ bug: "306666082"
+ is_fixed_read_only: true
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
index 2c49303..2db3e65 100644
--- a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
+++ b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
@@ -16,6 +16,8 @@
package com.android.internal.accessibility.common;
+import android.os.SystemProperties;
+
/**
* Collection of common constants for accessibility magnification.
*/
@@ -31,6 +33,7 @@
/** Minimum supported value for magnification scale. */
public static final float SCALE_MIN_VALUE = 1.0f;
- /** Maximum supported value for magnification scale. */
- public static final float SCALE_MAX_VALUE = 8.0f;
+ /** Maximum supported value for magnification scale. Default of 8.0. */
+ public static final float SCALE_MAX_VALUE =
+ Float.parseFloat(SystemProperties.get("ro.config.max_magnification_scale", "8.0"));
}
diff --git a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
index 7ec8838..c08968d 100644
--- a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
+++ b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
@@ -16,10 +16,23 @@
package com.android.internal.accessibility.common;
+import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.FONT_SIZE_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.FONT_SIZE_TILE_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.ONE_HANDED_TILE_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_TILE_SERVICE_COMPONENT_NAME;
+
import android.annotation.IntDef;
+import android.content.ComponentName;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
/**
* Collection of common constants for accessibility shortcut.
@@ -44,6 +57,10 @@
* choose accessibility shortcut as preferred shortcut.
* {@code TRIPLETAP} for displaying specifying magnification to be toggled via quickly
* tapping screen 3 times as preferred shortcut.
+ * {@code TWOFINGER_DOUBLETAP} for displaying specifying magnification to be toggled via
+ * quickly tapping screen 2 times with two fingers as preferred shortcut.
+ * {@code QUICK_SETTINGS} for displaying specifying the accessibility services or features which
+ * choose Quick Settings as preferred shortcut.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
@@ -51,12 +68,18 @@
UserShortcutType.SOFTWARE,
UserShortcutType.HARDWARE,
UserShortcutType.TRIPLETAP,
+ UserShortcutType.TWOFINGER_DOUBLETAP,
+ UserShortcutType.QUICK_SETTINGS,
})
public @interface UserShortcutType {
int DEFAULT = 0;
- int SOFTWARE = 1; // 1 << 0
- int HARDWARE = 2; // 1 << 1
- int TRIPLETAP = 4; // 1 << 2
+ // LINT.IfChange(shortcut_type_intdef)
+ int SOFTWARE = 1 << 0;
+ int HARDWARE = 1 << 1;
+ int TRIPLETAP = 1 << 2;
+ int TWOFINGER_DOUBLETAP = 1 << 3;
+ int QUICK_SETTINGS = 1 << 4;
+ // LINT.ThenChange(:shortcut_type_array)
}
/**
@@ -64,9 +87,13 @@
* non-default IntDef types.
*/
public static final int[] USER_SHORTCUT_TYPES = {
+ // LINT.IfChange(shortcut_type_array)
UserShortcutType.SOFTWARE,
UserShortcutType.HARDWARE,
- UserShortcutType.TRIPLETAP
+ UserShortcutType.TRIPLETAP,
+ UserShortcutType.TWOFINGER_DOUBLETAP,
+ UserShortcutType.QUICK_SETTINGS,
+ // LINT.ThenChange(:shortcut_type_intdef)
};
@@ -109,4 +136,30 @@
int LAUNCH = 0;
int EDIT = 1;
}
+
+ /**
+ * Annotation for different FAB shortcut type's menu size
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ FloatingMenuSize.UNKNOWN,
+ FloatingMenuSize.SMALL,
+ FloatingMenuSize.LARGE,
+ })
+ public @interface FloatingMenuSize {
+ int UNKNOWN = -1;
+ int SMALL = 0;
+ int LARGE = 1;
+ }
+
+ /**
+ * A map of a11y feature to its qs tile component
+ */
+ public static final Map<ComponentName, ComponentName> A11Y_FEATURE_TO_FRAMEWORK_TILE = Map.of(
+ COLOR_INVERSION_COMPONENT_NAME, COLOR_INVERSION_TILE_COMPONENT_NAME,
+ DALTONIZER_COMPONENT_NAME, DALTONIZER_TILE_COMPONENT_NAME,
+ ONE_HANDED_COMPONENT_NAME, ONE_HANDED_TILE_COMPONENT_NAME,
+ REDUCE_BRIGHT_COLORS_COMPONENT_NAME, REDUCE_BRIGHT_COLORS_TILE_SERVICE_COMPONENT_NAME,
+ FONT_SIZE_COMPONENT_NAME, FONT_SIZE_TILE_COMPONENT_NAME
+ );
}
diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
index 4f9fc39..e8472d4 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
@@ -70,6 +70,13 @@
public @interface A11yTextChangeType {
}
+ /** Denotes the accessibility enabled status */
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface State {
+ int OFF = 0;
+ int ON = 1;
+ }
+
/** Specifies no content has been changed for accessibility. */
public static final int NONE = 0;
/** Specifies some readable sequence has been changed. */
diff --git a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
index 3fd3030..f9c4d37 100644
--- a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
+++ b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
@@ -53,10 +53,13 @@
* Opts in component id into colon-separated {@link UserShortcutType}
* key's string from Settings.
*
- * @param context The current context.
+ * @param context The current context.
* @param shortcutType The preferred shortcut type user selected.
- * @param componentId The component id that need to be opted in Settings.
+ * @param componentId The component id that need to be opted in Settings.
+ * @deprecated Use
+ * {@link AccessibilityManager#enableShortcutsForTargets(boolean, int, Set, int)}
*/
+ @Deprecated
public static void optInValueToSettings(Context context, @UserShortcutType int shortcutType,
@NonNull String componentId) {
final StringJoiner joiner = new StringJoiner(String.valueOf(SERVICES_SEPARATOR));
@@ -83,7 +86,11 @@
* @param context The current context.
* @param shortcutType The preferred shortcut type user selected.
* @param componentId The component id that need to be opted out of Settings.
+ *
+ * @deprecated Use
+ * {@link AccessibilityManager#enableShortcutForTargets(boolean, int, Set, int)}
*/
+ @Deprecated
public static void optOutValueFromSettings(
Context context, @UserShortcutType int shortcutType, @NonNull String componentId) {
final StringJoiner joiner = new StringJoiner(String.valueOf(SERVICES_SEPARATOR));
@@ -166,6 +173,10 @@
return Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
case UserShortcutType.TRIPLETAP:
return Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED;
+ case UserShortcutType.TWOFINGER_DOUBLETAP:
+ return Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED;
+ case UserShortcutType.QUICK_SETTINGS:
+ return Settings.Secure.ACCESSIBILITY_QS_TARGETS;
default:
throw new IllegalArgumentException(
"Unsupported user shortcut type: " + type);
@@ -252,10 +263,13 @@
* If you just want to know the current state, you can use
* {@link AccessibilityManager#getAccessibilityShortcutTargets(int)}
*/
+ @NonNull
public static Set<String> getShortcutTargetsFromSettings(
Context context, @UserShortcutType int shortcutType, int userId) {
final String targetKey = convertToKey(shortcutType);
- if (Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED.equals(targetKey)) {
+ if (Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED.equals(targetKey)
+ || Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED
+ .equals(targetKey)) {
boolean magnificationEnabled = Settings.Secure.getIntForUser(
context.getContentResolver(), targetKey, /* def= */ 0, userId) == 1;
return magnificationEnabled ? Set.of(MAGNIFICATION_CONTROLLER_NAME)
diff --git a/core/java/com/android/internal/compat/Android.bp b/core/java/com/android/internal/compat/Android.bp
new file mode 100644
index 0000000..9ff05a6
--- /dev/null
+++ b/core/java/com/android/internal/compat/Android.bp
@@ -0,0 +1,7 @@
+aconfig_declarations {
+ name: "compat_logging_flags",
+ package: "com.android.internal.compat.flags",
+ srcs: [
+ "compat_logging_flags.aconfig",
+ ],
+}
diff --git a/core/java/com/android/internal/compat/ChangeReporter.java b/core/java/com/android/internal/compat/ChangeReporter.java
index b9d3df6..6ff546f 100644
--- a/core/java/com/android/internal/compat/ChangeReporter.java
+++ b/core/java/com/android/internal/compat/ChangeReporter.java
@@ -24,6 +24,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.compat.flags.Flags;
import com.android.internal.util.FrameworkStatsLog;
import java.lang.annotation.Retention;
@@ -40,7 +41,7 @@
* @hide
*/
public final class ChangeReporter {
- private static final String TAG = "CompatibilityChangeReporter";
+ private static final String TAG = "CompatChangeReporter";
private int mSource;
private static final class ChangeReport {
@@ -84,19 +85,34 @@
* Report the change to stats log and to the debug log if the change was not previously
* logged already.
*
+ * @param uid affected by the change
+ * @param changeId the reported change id
+ * @param state of the reported change - enabled/disabled/only logged
+ * @param isLoggableBySdk whether debug logging is allowed for this change based on target
+ * SDK version. This is combined with other logic to determine whether to
+ * actually log. If the sdk version does not matter, should be true.
+ */
+ public void reportChange(int uid, long changeId, int state, boolean isLoggableBySdk) {
+ if (shouldWriteToStatsLog(uid, changeId, state)) {
+ FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid,
+ changeId, state, mSource);
+ }
+ if (shouldWriteToDebug(uid, changeId, state, isLoggableBySdk)) {
+ debugLog(uid, changeId, state);
+ }
+ markAsReported(uid, new ChangeReport(changeId, state));
+ }
+
+ /**
+ * Report the change to stats log and to the debug log if the change was not previously
+ * logged already.
+ *
* @param uid affected by the change
* @param changeId the reported change id
* @param state of the reported change - enabled/disabled/only logged
*/
public void reportChange(int uid, long changeId, int state) {
- if (shouldWriteToStatsLog(uid, changeId, state)) {
- FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid,
- changeId, state, mSource);
- }
- if (shouldWriteToDebug(uid, changeId, state)) {
- debugLog(uid, changeId, state);
- }
- markAsReported(uid, new ChangeReport(changeId, state));
+ reportChange(uid, changeId, state, true);
}
/**
@@ -130,14 +146,43 @@
/**
* Returns whether the next report should be logged to logcat.
*
- * @param uid affected by the change
- * @param changeId the reported change id
- * @param state of the reported change - enabled/disabled/only logged
+ * @param uid affected by the change
+ * @param changeId the reported change id
+ * @param state of the reported change - enabled/disabled/only logged
+ * @param isLoggableBySdk whether debug logging is allowed for this change based on target
+ * SDK version. This is combined with other logic to determine whether to
+ * actually log. If the sdk version does not matter, should be true.
+ * @return true if the report should be logged
+ */
+ @VisibleForTesting
+ public boolean shouldWriteToDebug(
+ int uid, long changeId, int state, boolean isLoggableBySdk) {
+ // If log all bit is on, always return true.
+ if (mDebugLogAll) return true;
+ // If the change has already been reported, do not write.
+ if (isAlreadyReported(uid, new ChangeReport(changeId, state))) return false;
+
+ // If the flag is turned off or the TAG's logging is forced to debug level with
+ // `adb setprop log.tag.CompatChangeReporter=DEBUG`, write to debug since the above checks
+ // have already passed.
+ boolean skipLoggingFlag = Flags.skipOldAndDisabledCompatLogging();
+ if (!skipLoggingFlag || Log.isLoggable(TAG, Log.DEBUG)) return true;
+
+ // Log if the change is enabled and targets the latest sdk version.
+ return isLoggableBySdk && state != STATE_DISABLED;
+ }
+
+ /**
+ * Returns whether the next report should be logged to logcat.
+ *
+ * @param uid affected by the change
+ * @param changeId the reported change id
+ * @param state of the reported change - enabled/disabled/only logged
* @return true if the report should be logged
*/
@VisibleForTesting
public boolean shouldWriteToDebug(int uid, long changeId, int state) {
- return mDebugLogAll || !isAlreadyReported(uid, new ChangeReport(changeId, state));
+ return shouldWriteToDebug(uid, changeId, state, true);
}
private boolean isAlreadyReported(int uid, ChangeReport report) {
diff --git a/core/java/com/android/internal/compat/compat_logging_flags.aconfig b/core/java/com/android/internal/compat/compat_logging_flags.aconfig
new file mode 100644
index 0000000..fab3856
--- /dev/null
+++ b/core/java/com/android/internal/compat/compat_logging_flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.internal.compat.flags"
+
+flag {
+ name: "skip_old_and_disabled_compat_logging"
+ namespace: "platform_compat"
+ description: "Feature flag for skipping debug logging for changes that do not target the latest sdk or are disabled"
+ bug: "323949942"
+ is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
index 3020d77..0e4f04f 100644
--- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java
+++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
@@ -17,8 +17,6 @@
package com.android.internal.inputmethod;
import static android.view.inputmethod.InputConnectionProto.CURSOR_CAPS_MODE;
-import static android.view.inputmethod.InputConnectionProto.EDITABLE_TEXT;
-import static android.view.inputmethod.InputConnectionProto.SELECTED_TEXT;
import static android.view.inputmethod.InputConnectionProto.SELECTED_TEXT_END;
import static android.view.inputmethod.InputConnectionProto.SELECTED_TEXT_START;
@@ -335,16 +333,6 @@
@Override
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- CharSequence editableText = mTextView.getText();
- CharSequence selectedText = getSelectedText(0 /* flags */);
- if (InputConnectionProtoDumper.DUMP_TEXT) {
- if (editableText != null) {
- proto.write(EDITABLE_TEXT, editableText.toString());
- }
- if (selectedText != null) {
- proto.write(SELECTED_TEXT, selectedText.toString());
- }
- }
final Editable content = getEditable();
if (content != null) {
int start = Selection.getSelectionStart(content);
diff --git a/core/java/com/android/internal/inputmethod/InputConnectionProtoDumper.java b/core/java/com/android/internal/inputmethod/InputConnectionProtoDumper.java
index 7172d0a..31cf758 100644
--- a/core/java/com/android/internal/inputmethod/InputConnectionProtoDumper.java
+++ b/core/java/com/android/internal/inputmethod/InputConnectionProtoDumper.java
@@ -44,7 +44,6 @@
*/
public final class InputConnectionProtoDumper {
static final String TAG = "InputConnectionProtoDumper";
- public static final boolean DUMP_TEXT = false;
private InputConnectionProtoDumper() {}
@@ -67,11 +66,6 @@
final long token = proto.start(GET_TEXT_AFTER_CURSOR);
proto.write(GetTextAfterCursor.LENGTH, length);
proto.write(GetTextAfterCursor.FLAGS, flags);
- if (result == null) {
- proto.write(GetTextAfterCursor.RESULT, "null result");
- } else if (DUMP_TEXT) {
- proto.write(GetTextAfterCursor.RESULT, result.toString());
- }
proto.end(token);
return proto.getBytes();
}
@@ -95,11 +89,6 @@
final long token = proto.start(GET_TEXT_BEFORE_CURSOR);
proto.write(GetTextBeforeCursor.LENGTH, length);
proto.write(GetTextBeforeCursor.FLAGS, flags);
- if (result == null) {
- proto.write(GetTextBeforeCursor.RESULT, "null result");
- } else if (DUMP_TEXT) {
- proto.write(GetTextBeforeCursor.RESULT, result.toString());
- }
proto.end(token);
return proto.getBytes();
}
@@ -122,11 +111,6 @@
ProtoOutputStream proto = new ProtoOutputStream();
final long token = proto.start(GET_SELECTED_TEXT);
proto.write(GetSelectedText.FLAGS, flags);
- if (result == null) {
- proto.write(GetSelectedText.RESULT, "null result");
- } else if (DUMP_TEXT) {
- proto.write(GetSelectedText.RESULT, result.toString());
- }
proto.end(token);
return proto.getBytes();
}
@@ -155,13 +139,8 @@
proto.write(GetSurroundingText.BEFORE_LENGTH, beforeLength);
proto.write(GetSurroundingText.AFTER_LENGTH, afterLength);
proto.write(GetSurroundingText.FLAGS, flags);
- if (result == null) {
+ if (result != null) {
final long token_result = proto.start(GetSurroundingText.RESULT);
- proto.write(GetSurroundingText.SurroundingText.TEXT, "null result");
- proto.end(token_result);
- } else if (DUMP_TEXT) {
- final long token_result = proto.start(GetSurroundingText.RESULT);
- proto.write(GetSurroundingText.SurroundingText.TEXT, result.getText().toString());
proto.write(GetSurroundingText.SurroundingText.SELECTION_START,
result.getSelectionStart());
proto.write(GetSurroundingText.SurroundingText.SELECTION_END,
@@ -188,9 +167,7 @@
ProtoOutputStream proto = new ProtoOutputStream();
final long token = proto.start(GET_CURSOR_CAPS_MODE);
proto.write(GetCursorCapsMode.REQ_MODES, reqModes);
- if (DUMP_TEXT) {
- proto.write(GetCursorCapsMode.RESULT, result);
- }
+ proto.write(GetCursorCapsMode.RESULT, result);
proto.end(token);
return proto.getBytes();
}
@@ -223,11 +200,6 @@
proto.write(GetExtractedText.ExtractedTextRequest.HINT_MAX_CHARS, request.hintMaxChars);
proto.end(token_request);
proto.write(GetExtractedText.FLAGS, flags);
- if (result == null) {
- proto.write(GetExtractedText.RESULT, "null result");
- } else if (DUMP_TEXT) {
- proto.write(GetExtractedText.RESULT, result.text.toString());
- }
proto.end(token);
return proto.getBytes();
}
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index cbe0700..d4dcec9 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -93,6 +93,9 @@
throw ex;
}
+ if (peer.getUid() != Process.SYSTEM_UID) {
+ throw new ZygoteSecurityException("Only system UID is allowed to connect to Zygote.");
+ }
isEof = false;
}
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 8566263..0f1f7e9 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -29,6 +29,7 @@
import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
import static android.view.Window.DECOR_CAPTION_SHADE_DARK;
import static android.view.Window.DECOR_CAPTION_SHADE_LIGHT;
+import static android.view.WindowInsetsController.APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
@@ -136,6 +137,8 @@
private static final int SCRIM_LIGHT = 0xe6ffffff; // 90% white
+ private static final int SCRIM_ALPHA = 0xcc0000; // 80% alpha
+
public static final ColorViewAttributes STATUS_BAR_COLOR_VIEW_ATTRIBUTES =
new ColorViewAttributes(FLAG_TRANSLUCENT_STATUS,
Gravity.TOP, Gravity.LEFT, Gravity.RIGHT,
@@ -989,6 +992,16 @@
if (mOriginalBackgroundDrawable != drawable) {
mOriginalBackgroundDrawable = drawable;
updateBackgroundDrawable();
+ if (mWindow.mEdgeToEdgeEnforced && !mWindow.mNavigationBarColorSpecified
+ && drawable instanceof ColorDrawable) {
+ final int color = ((ColorDrawable) drawable).getColor();
+ final boolean lightBar = Color.valueOf(color).luminance() > 0.5f;
+ getWindowInsetsController().setSystemBarsAppearance(
+ lightBar ? APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS : 0,
+ APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS);
+ mWindow.mNavigationBarColor = color;
+ updateColorViews(null /* insets */, false /* animate */);
+ }
if (drawable != null) {
mResizingBackgroundDrawable = enforceNonTranslucentBackground(drawable,
mWindow.isTranslucent() || mWindow.isShowingWallpaper());
@@ -1407,7 +1420,8 @@
mSemiTransparentBarColor, mWindow.mStatusBarColor,
appearance, APPEARANCE_LIGHT_STATUS_BARS,
mWindow.mEnsureStatusBarContrastWhenTransparent
- && (mLastSuppressScrimTypes & WindowInsets.Type.statusBars()) == 0);
+ && (mLastSuppressScrimTypes & WindowInsets.Type.statusBars()) == 0,
+ false /* movesBarColorToScrim */);
}
private int calculateNavigationBarColor(@Appearance int appearance) {
@@ -1415,22 +1429,29 @@
mSemiTransparentBarColor, mWindow.mNavigationBarColor,
appearance, APPEARANCE_LIGHT_NAVIGATION_BARS,
mWindow.mEnsureNavigationBarContrastWhenTransparent
- && (mLastSuppressScrimTypes & WindowInsets.Type.navigationBars()) == 0);
+ && (mLastSuppressScrimTypes & WindowInsets.Type.navigationBars()) == 0,
+ mWindow.mEdgeToEdgeEnforced);
}
public static int calculateBarColor(int flags, int translucentFlag, int semiTransparentBarColor,
int barColor, @Appearance int appearance, @Appearance int lightAppearanceFlag,
- boolean scrimTransparent) {
+ boolean ensuresContrast, boolean movesBarColorToScrim) {
if ((flags & translucentFlag) != 0) {
return semiTransparentBarColor;
} else if ((flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0) {
return Color.BLACK;
- } else if (scrimTransparent && Color.alpha(barColor) == 0) {
- boolean light = (appearance & lightAppearanceFlag) != 0;
- return light ? SCRIM_LIGHT : semiTransparentBarColor;
- } else {
- return barColor;
+ } else if (ensuresContrast) {
+ final int alpha = Color.alpha(barColor);
+ if (alpha == 0) {
+ boolean light = (appearance & lightAppearanceFlag) != 0;
+ return light ? SCRIM_LIGHT : semiTransparentBarColor;
+ } else if (movesBarColorToScrim) {
+ return (barColor & 0xffffff) | SCRIM_ALPHA;
+ }
+ } else if (movesBarColorToScrim) {
+ return Color.TRANSPARENT;
}
+ return barColor;
}
private int getCurrentColor(ColorViewState state) {
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 0dd01e4..9868ceb 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -21,6 +21,7 @@
import static android.view.View.SYSTEM_UI_LAYOUT_FLAGS;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowInsetsController.APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
@@ -298,6 +299,7 @@
int mStatusBarColor = Color.TRANSPARENT;
int mNavigationBarColor = Color.TRANSPARENT;
int mNavigationBarDividerColor = Color.TRANSPARENT;
+ boolean mNavigationBarColorSpecified = false;
private boolean mForcedStatusBarColor = false;
private boolean mForcedNavigationBarColor = false;
@@ -370,7 +372,7 @@
boolean mDecorFitsSystemWindows = true;
- private boolean mEdgeToEdgeEnforced;
+ boolean mEdgeToEdgeEnforced;
private final ProxyOnBackInvokedDispatcher mProxyOnBackInvokedDispatcher;
@@ -406,6 +408,7 @@
mElevation = preservedWindow.getElevation();
mLoadElevation = false;
mForceDecorInstall = true;
+ setSystemBarAppearance(preservedWindow.getSystemBarAppearance());
// If we're preserving window, carry over the app token from the preserved
// window, as we'll be skipping the addView in handleResumeActivity(), and
// the token will not be updated as for a new window.
@@ -2577,21 +2580,27 @@
if (!mForcedStatusBarColor && !mEdgeToEdgeEnforced) {
mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, Color.BLACK);
}
- if (!mForcedNavigationBarColor && !mEdgeToEdgeEnforced) {
+ if (!mForcedNavigationBarColor) {
final int navBarCompatibleColor = context.getColor(R.color.navigation_bar_compatible);
final int navBarDefaultColor = context.getColor(R.color.navigation_bar_default);
final int navBarColor = a.getColor(R.styleable.Window_navigationBarColor,
navBarDefaultColor);
+ final boolean navigationBarColorSpecified = navBarColor != navBarDefaultColor;
mNavigationBarColor =
- navBarColor == navBarDefaultColor
+ !navigationBarColorSpecified
+ && !mEdgeToEdgeEnforced
&& !context.getResources().getBoolean(
R.bool.config_navBarDefaultTransparent)
? navBarCompatibleColor
: navBarColor;
- mNavigationBarDividerColor = a.getColor(R.styleable.Window_navigationBarDividerColor,
- Color.TRANSPARENT);
+ mNavigationBarColorSpecified |= navigationBarColorSpecified;
+
+ if (!mEdgeToEdgeEnforced) {
+ mNavigationBarDividerColor = a.getColor(
+ R.styleable.Window_navigationBarDividerColor, Color.TRANSPARENT);
+ }
}
if (!targetPreQ) {
mEnsureStatusBarContrastWhenTransparent = a.getBoolean(
@@ -3941,17 +3950,20 @@
@Override
public void setNavigationBarColor(int color) {
- if (mEdgeToEdgeEnforced) {
- return;
- }
if (mNavigationBarColor == color && mForcedNavigationBarColor) {
return;
}
mNavigationBarColor = color;
mForcedNavigationBarColor = true;
+ mNavigationBarColorSpecified = true;
if (mDecor != null) {
+ mDecor.getWindowInsetsController().setSystemBarsAppearance(
+ 0, APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS);
mDecor.updateColorViews(null, false /* animate */);
}
+ if (mEdgeToEdgeEnforced) {
+ return;
+ }
final WindowControllerCallback callback = getWindowControllerCallback();
if (callback != null) {
getWindowControllerCallback().updateNavigationBarColor(color);
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 6ffc638..a2efbd2 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -370,9 +370,10 @@
/**
* Enters stage split from a current running app.
*
+ * @param displayId the id of the current display.
* @param leftOrTop indicates where the stage split is.
*/
- void enterStageSplitFromRunningApp(boolean leftOrTop);
+ void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop);
/**
* Shows the media output switcher dialog.
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index dc3b5a8..0257033 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -33,8 +33,10 @@
import com.android.internal.inputmethod.InputBindResult;
/**
- * Public interface to the global input method manager, used by all client
- * applications.
+ * Public interface to the global input method manager, used by all client applications.
+ *
+ * When adding new methods, make sure the associated user can be inferred from the arguments.
+ * Consider passing the associated userId when not already passing a display id or a window token.
*/
interface IInputMethodManager {
void addClient(in IInputMethodClient client, in IRemoteInputConnection inputmethod,
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 76e7138..a0dc94f 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -93,6 +93,7 @@
shared_libs: [
"libbase",
"libcutils",
+ "libtracing_perfetto",
"libharfbuzz_ng",
"liblog",
"libminikin",
@@ -358,6 +359,7 @@
"libimage_io",
"libultrahdr",
"libperfetto_c",
+ "libtracing_perfetto",
],
export_shared_lib_headers: [
// our headers include libnativewindow's public headers
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 1504a00..a98f947 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -410,6 +410,7 @@
stats[sub_heap].swappedOut += usage.swap;
stats[sub_heap].swappedOutPss += usage.swap_pss;
}
+ return true;
};
return meminfo::ForEachVmaFromFile(smaps_path, vma_scan);
diff --git a/core/jni/android_os_Trace.cpp b/core/jni/android_os_Trace.cpp
index ffacd9c..b579daf 100644
--- a/core/jni/android_os_Trace.cpp
+++ b/core/jni/android_os_Trace.cpp
@@ -14,11 +14,11 @@
* limitations under the License.
*/
+#include <cutils/compiler.h>
#include <jni.h>
-
-#include <cutils/trace.h>
#include <log/log.h>
#include <nativehelper/JNIHelp.h>
+#include <tracing_perfetto.h>
#include <array>
@@ -59,33 +59,30 @@
static void android_os_Trace_nativeTraceCounter(JNIEnv* env, jclass,
jlong tag, jstring nameStr, jlong value) {
- withString(env, nameStr, [tag, value](const char* str) {
- atrace_int64(tag, str, value);
- });
+ withString(env, nameStr,
+ [tag, value](const char* str) { tracing_perfetto::traceCounter(tag, str, value); });
}
static void android_os_Trace_nativeTraceBegin(JNIEnv* env, jclass,
jlong tag, jstring nameStr) {
- withString(env, nameStr, [tag](const char* str) {
- atrace_begin(tag, str);
- });
+ withString(env, nameStr, [tag](const char* str) { tracing_perfetto::traceBegin(tag, str); });
}
static void android_os_Trace_nativeTraceEnd(JNIEnv*, jclass, jlong tag) {
- atrace_end(tag);
+ tracing_perfetto::traceEnd(tag);
}
static void android_os_Trace_nativeAsyncTraceBegin(JNIEnv* env, jclass,
jlong tag, jstring nameStr, jint cookie) {
withString(env, nameStr, [tag, cookie](const char* str) {
- atrace_async_begin(tag, str, cookie);
+ tracing_perfetto::traceAsyncBegin(tag, str, cookie);
});
}
static void android_os_Trace_nativeAsyncTraceEnd(JNIEnv* env, jclass,
jlong tag, jstring nameStr, jint cookie) {
withString(env, nameStr, [tag, cookie](const char* str) {
- atrace_async_end(tag, str, cookie);
+ tracing_perfetto::traceAsyncEnd(tag, str, cookie);
});
}
@@ -93,7 +90,7 @@
jlong tag, jstring trackStr, jstring nameStr, jint cookie) {
withString(env, trackStr, [env, tag, nameStr, cookie](const char* track) {
withString(env, nameStr, [tag, track, cookie](const char* name) {
- atrace_async_for_track_begin(tag, track, name, cookie);
+ tracing_perfetto::traceAsyncBeginForTrack(tag, name, track, cookie);
});
});
}
@@ -101,77 +98,66 @@
static void android_os_Trace_nativeAsyncTraceForTrackEnd(JNIEnv* env, jclass,
jlong tag, jstring trackStr, jint cookie) {
withString(env, trackStr, [tag, cookie](const char* track) {
- atrace_async_for_track_end(tag, track, cookie);
+ tracing_perfetto::traceAsyncEndForTrack(tag, track, cookie);
});
}
static void android_os_Trace_nativeSetAppTracingAllowed(JNIEnv*, jclass, jboolean allowed) {
- atrace_update_tags();
+ // no-op
}
static void android_os_Trace_nativeSetTracingEnabled(JNIEnv*, jclass, jboolean enabled) {
- atrace_set_tracing_enabled(enabled);
+ // no-op
}
static void android_os_Trace_nativeInstant(JNIEnv* env, jclass,
jlong tag, jstring nameStr) {
- withString(env, nameStr, [tag](const char* str) {
- atrace_instant(tag, str);
- });
+ withString(env, nameStr, [tag](const char* str) { tracing_perfetto::traceInstant(tag, str); });
}
static void android_os_Trace_nativeInstantForTrack(JNIEnv* env, jclass,
jlong tag, jstring trackStr, jstring nameStr) {
withString(env, trackStr, [env, tag, nameStr](const char* track) {
withString(env, nameStr, [tag, track](const char* name) {
- atrace_instant_for_track(tag, track, name);
+ tracing_perfetto::traceInstantForTrack(tag, track, name);
});
});
}
+static jlong android_os_Trace_nativeGetEnabledTags(JNIEnv* env) {
+ return tracing_perfetto::getEnabledCategories();
+}
+
+static void android_os_Trace_nativeRegisterWithPerfetto(JNIEnv* env) {
+ tracing_perfetto::registerWithPerfetto();
+}
+
static const JNINativeMethod gTraceMethods[] = {
- /* name, signature, funcPtr */
- { "nativeSetAppTracingAllowed",
- "(Z)V",
- (void*)android_os_Trace_nativeSetAppTracingAllowed },
- { "nativeSetTracingEnabled",
- "(Z)V",
- (void*)android_os_Trace_nativeSetTracingEnabled },
+ /* name, signature, funcPtr */
+ {"nativeSetAppTracingAllowed", "(Z)V", (void*)android_os_Trace_nativeSetAppTracingAllowed},
+ {"nativeSetTracingEnabled", "(Z)V", (void*)android_os_Trace_nativeSetTracingEnabled},
- // ----------- @FastNative ----------------
+ // ----------- @FastNative ----------------
- { "nativeTraceCounter",
- "(JLjava/lang/String;J)V",
- (void*)android_os_Trace_nativeTraceCounter },
- { "nativeTraceBegin",
- "(JLjava/lang/String;)V",
- (void*)android_os_Trace_nativeTraceBegin },
- { "nativeTraceEnd",
- "(J)V",
- (void*)android_os_Trace_nativeTraceEnd },
- { "nativeAsyncTraceBegin",
- "(JLjava/lang/String;I)V",
- (void*)android_os_Trace_nativeAsyncTraceBegin },
- { "nativeAsyncTraceEnd",
- "(JLjava/lang/String;I)V",
- (void*)android_os_Trace_nativeAsyncTraceEnd },
- { "nativeAsyncTraceForTrackBegin",
- "(JLjava/lang/String;Ljava/lang/String;I)V",
- (void*)android_os_Trace_nativeAsyncTraceForTrackBegin },
- { "nativeAsyncTraceForTrackEnd",
- "(JLjava/lang/String;I)V",
- (void*)android_os_Trace_nativeAsyncTraceForTrackEnd },
- { "nativeInstant",
- "(JLjava/lang/String;)V",
- (void*)android_os_Trace_nativeInstant },
- { "nativeInstantForTrack",
- "(JLjava/lang/String;Ljava/lang/String;)V",
- (void*)android_os_Trace_nativeInstantForTrack },
+ {"nativeTraceCounter", "(JLjava/lang/String;J)V",
+ (void*)android_os_Trace_nativeTraceCounter},
+ {"nativeTraceBegin", "(JLjava/lang/String;)V", (void*)android_os_Trace_nativeTraceBegin},
+ {"nativeTraceEnd", "(J)V", (void*)android_os_Trace_nativeTraceEnd},
+ {"nativeAsyncTraceBegin", "(JLjava/lang/String;I)V",
+ (void*)android_os_Trace_nativeAsyncTraceBegin},
+ {"nativeAsyncTraceEnd", "(JLjava/lang/String;I)V",
+ (void*)android_os_Trace_nativeAsyncTraceEnd},
+ {"nativeAsyncTraceForTrackBegin", "(JLjava/lang/String;Ljava/lang/String;I)V",
+ (void*)android_os_Trace_nativeAsyncTraceForTrackBegin},
+ {"nativeAsyncTraceForTrackEnd", "(JLjava/lang/String;I)V",
+ (void*)android_os_Trace_nativeAsyncTraceForTrackEnd},
+ {"nativeInstant", "(JLjava/lang/String;)V", (void*)android_os_Trace_nativeInstant},
+ {"nativeInstantForTrack", "(JLjava/lang/String;Ljava/lang/String;)V",
+ (void*)android_os_Trace_nativeInstantForTrack},
+ {"nativeRegisterWithPerfetto", "()V", (void*)android_os_Trace_nativeRegisterWithPerfetto},
- // ----------- @CriticalNative ----------------
- { "nativeGetEnabledTags",
- "()J",
- (void*)atrace_get_enabled_tags },
+ // ----------- @CriticalNative ----------------
+ {"nativeGetEnabledTags", "()J", (void*)android_os_Trace_nativeGetEnabledTags},
};
int register_android_os_Trace(JNIEnv* env) {
diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp
index b6bf617..7c976b7 100644
--- a/core/jni/android_text_Hyphenator.cpp
+++ b/core/jni/android_text_Hyphenator.cpp
@@ -36,41 +36,43 @@
return SYSTEM_HYPHENATOR_PREFIX + lowerLocale + SYSTEM_HYPHENATOR_SUFFIX;
}
-static const uint8_t* mmapPatternFile(const std::string& locale) {
+static std::pair<const uint8_t*, uint32_t> mmapPatternFile(const std::string& locale) {
const std::string hyFilePath = buildFileName(locale);
const int fd = open(hyFilePath.c_str(), O_RDONLY | O_CLOEXEC);
if (fd == -1) {
- return nullptr; // Open failed.
+ return std::make_pair(nullptr, 0); // Open failed.
}
struct stat st = {};
if (fstat(fd, &st) == -1) { // Unlikely to happen.
close(fd);
- return nullptr;
+ return std::make_pair(nullptr, 0);
}
void* ptr = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0 /* offset */);
close(fd);
if (ptr == MAP_FAILED) {
- return nullptr;
+ return std::make_pair(nullptr, 0);
}
- return reinterpret_cast<const uint8_t*>(ptr);
+ return std::make_pair(reinterpret_cast<const uint8_t*>(ptr), st.st_size);
}
static void addHyphenatorWithoutPatternFile(const std::string& locale, int minPrefix,
int minSuffix) {
- minikin::addHyphenator(locale, minikin::Hyphenator::loadBinary(
- nullptr, minPrefix, minSuffix, locale));
+ minikin::addHyphenator(locale,
+ minikin::Hyphenator::loadBinary(nullptr, 0, minPrefix, minSuffix,
+ locale));
}
static void addHyphenator(const std::string& locale, int minPrefix, int minSuffix) {
- const uint8_t* ptr = mmapPatternFile(locale);
+ auto [ptr, size] = mmapPatternFile(locale);
if (ptr == nullptr) {
ALOGE("Unable to find pattern file or unable to map it for %s", locale.c_str());
return;
}
- minikin::addHyphenator(locale, minikin::Hyphenator::loadBinary(
- ptr, minPrefix, minSuffix, locale));
+ minikin::addHyphenator(locale,
+ minikin::Hyphenator::loadBinary(ptr, size, minPrefix, minSuffix,
+ locale));
}
static void addHyphenatorAlias(const std::string& from, const std::string& to) {
diff --git a/core/jni/android_tracing_PerfettoDataSource.cpp b/core/jni/android_tracing_PerfettoDataSource.cpp
index 16c3ca9..1eff5ce 100644
--- a/core/jni/android_tracing_PerfettoDataSource.cpp
+++ b/core/jni/android_tracing_PerfettoDataSource.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define LOG_TAG "Perfetto"
+#define LOG_TAG "NativeJavaPerfettoDs"
#include "android_tracing_PerfettoDataSource.h"
@@ -166,16 +166,25 @@
void PerfettoDataSource::trace(JNIEnv* env, jobject traceFunction) {
PERFETTO_DS_TRACE(dataSource, ctx) {
+ ALOG(LOG_DEBUG, LOG_TAG, "\tin native trace callback function %p", this);
TlsState* tls_state =
reinterpret_cast<TlsState*>(PerfettoDsGetCustomTls(&dataSource, &ctx));
IncrementalState* incr_state = reinterpret_cast<IncrementalState*>(
PerfettoDsGetIncrementalState(&dataSource, &ctx));
+ ALOG(LOG_DEBUG, LOG_TAG, "\t tls_state = %p", tls_state);
+ ALOG(LOG_DEBUG, LOG_TAG, "\t incr_state = %p", incr_state);
+
+ ALOG(LOG_DEBUG, LOG_TAG, "\t tls_state->jobj = %p", tls_state->jobj);
+ ALOG(LOG_DEBUG, LOG_TAG, "\t incr_state->jobj = %p", incr_state->jobj);
+
ScopedLocalRef<jobject> jCtx(env,
env->NewObject(gTracingContextClassInfo.clazz,
gTracingContextClassInfo.init, &ctx,
tls_state->jobj, incr_state->jobj));
+ ALOG(LOG_DEBUG, LOG_TAG, "\t jCtx = %p", jCtx.get());
+
jclass objclass = env->GetObjectClass(traceFunction);
jmethodID method =
env->GetMethodID(objclass, "trace", "(Landroid/tracing/perfetto/TracingContext;)V");
@@ -209,7 +218,9 @@
jlong nativeCreate(JNIEnv* env, jclass clazz, jobject javaDataSource, jstring name) {
const char* nativeString = env->GetStringUTFChars(name, 0);
+ ALOG(LOG_DEBUG, LOG_TAG, "nativeCreate(%p, %s)", javaDataSource, nativeString);
PerfettoDataSource* dataSource = new PerfettoDataSource(env, javaDataSource, nativeString);
+ ALOG(LOG_DEBUG, LOG_TAG, "\tdatasource* = %p", dataSource);
env->ReleaseStringUTFChars(name, nativeString);
dataSource->incStrong((void*)nativeCreate);
@@ -218,33 +229,39 @@
}
void nativeDestroy(void* ptr) {
+ ALOG(LOG_DEBUG, LOG_TAG, "nativeCreate(%p)", ptr);
PerfettoDataSource* dataSource = reinterpret_cast<PerfettoDataSource*>(ptr);
dataSource->decStrong((void*)nativeCreate);
}
static jlong nativeGetFinalizer(JNIEnv* /* env */, jclass /* clazz */) {
+ ALOG(LOG_DEBUG, LOG_TAG, "nativeGetFinalizer()");
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&nativeDestroy));
}
void nativeTrace(JNIEnv* env, jclass clazz, jlong dataSourcePtr, jobject traceFunctionInterface) {
+ ALOG(LOG_DEBUG, LOG_TAG, "nativeTrace(%p)", (void*)dataSourcePtr);
sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
datasource->trace(env, traceFunctionInterface);
}
void nativeFlush(JNIEnv* env, jclass clazz, jobject jCtx, jlong ctxPtr) {
+ ALOG(LOG_DEBUG, LOG_TAG, "nativeFlush(%p, %p)", jCtx, (void*)ctxPtr);
auto* ctx = reinterpret_cast<struct PerfettoDsTracerIterator*>(ctxPtr);
traceAllPendingPackets(env, jCtx, ctx);
PerfettoDsTracerFlush(ctx, nullptr, nullptr);
}
void nativeFlushAll(JNIEnv* env, jclass clazz, jlong ptr) {
+ ALOG(LOG_DEBUG, LOG_TAG, "nativeFlushAll(%p)", (void*)ptr);
sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(ptr);
datasource->flushAll();
}
void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr,
int buffer_exhausted_policy) {
+ ALOG(LOG_DEBUG, LOG_TAG, "nativeRegisterDataSource(%p)", (void*)datasource_ptr);
sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(datasource_ptr);
struct PerfettoDsParams params = PerfettoDsParamsDefault();
@@ -267,6 +284,9 @@
auto* datasource_instance =
new PerfettoDataSourceInstance(env, java_data_source_instance.get(), inst_id);
+ ALOG(LOG_DEBUG, LOG_TAG, "on_setup_cb ds=%p, ds_instance=%p", datasource,
+ datasource_instance);
+
return static_cast<void*>(datasource_instance);
};
@@ -280,6 +300,8 @@
auto* tls_state = new TlsState(java_tls_state);
+ ALOG(LOG_DEBUG, LOG_TAG, "on_create_tls_cb ds=%p, tsl_state=%p", datasource, tls_state);
+
return static_cast<void*>(tls_state);
};
@@ -287,6 +309,9 @@
JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
TlsState* tls_state = reinterpret_cast<TlsState*>(ptr);
+
+ ALOG(LOG_DEBUG, LOG_TAG, "on_delete_tls_cb %p", tls_state);
+
env->DeleteGlobalRef(tls_state->jobj);
delete tls_state;
};
@@ -299,6 +324,9 @@
jobject java_incr_state = datasource->createIncrementalStateGlobalRef(env, inst_id);
auto* incr_state = new IncrementalState(java_incr_state);
+
+ ALOG(LOG_DEBUG, LOG_TAG, "on_create_incr_cb ds=%p, incr_state=%p", datasource, incr_state);
+
return static_cast<void*>(incr_state);
};
@@ -306,6 +334,9 @@
JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
IncrementalState* incr_state = reinterpret_cast<IncrementalState*>(ptr);
+
+ ALOG(LOG_DEBUG, LOG_TAG, "on_delete_incr_cb incr_state=%p", incr_state);
+
env->DeleteGlobalRef(incr_state->jobj);
delete incr_state;
};
@@ -315,6 +346,9 @@
JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+
+ ALOG(LOG_DEBUG, LOG_TAG, "on_start_cb ds_instance=%p", datasource_instance);
+
datasource_instance->onStart(env);
};
@@ -323,6 +357,9 @@
JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+
+ ALOG(LOG_DEBUG, LOG_TAG, "on_flush_cb ds_instance=%p", datasource_instance);
+
datasource_instance->onFlush(env);
};
@@ -331,12 +368,18 @@
JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+
+ ALOG(LOG_DEBUG, LOG_TAG, "on_stop_cb ds_instance=%p", datasource_instance);
+
datasource_instance->onStop(env);
};
params.on_destroy_cb = [](struct PerfettoDsImpl* ds_impl, void* user_arg,
void* inst_ctx) -> void {
auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+
+ ALOG(LOG_DEBUG, LOG_TAG, "on_destroy_cb ds_instance=%p", datasource_instance);
+
delete datasource_instance;
};
@@ -345,20 +388,28 @@
jobject nativeGetPerfettoInstanceLocked(JNIEnv* env, jclass clazz, jlong dataSourcePtr,
PerfettoDsInstanceIndex instance_idx) {
+ ALOG(LOG_DEBUG, LOG_TAG, "nativeGetPerfettoInstanceLocked ds=%p, idx=%d", (void*)dataSourcePtr,
+ instance_idx);
sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(
PerfettoDsImplGetInstanceLocked(datasource->dataSource.impl, instance_idx));
if (datasource_instance == nullptr) {
// datasource instance doesn't exist
+ ALOG(LOG_WARN, LOG_TAG,
+ "DS instance invalid!! nativeGetPerfettoInstanceLocked returning NULL");
return nullptr;
}
+ ALOG(LOG_DEBUG, LOG_TAG, "\tnativeGetPerfettoInstanceLocked got lock ds=%p, idx=%d",
+ (void*)dataSourcePtr, instance_idx);
return datasource_instance->GetJavaDataSourceInstance();
}
void nativeReleasePerfettoInstanceLocked(JNIEnv* env, jclass clazz, jlong dataSourcePtr,
PerfettoDsInstanceIndex instance_idx) {
+ ALOG(LOG_DEBUG, LOG_TAG, "nativeReleasePerfettoInstanceLocked got lock ds=%p, idx=%d",
+ (void*)dataSourcePtr, instance_idx);
sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
PerfettoDsImplReleaseInstanceLocked(datasource->dataSource.impl, instance_idx);
}
diff --git a/core/jni/android_tracing_PerfettoDataSource.h b/core/jni/android_tracing_PerfettoDataSource.h
index 61a7654..906d9f5 100644
--- a/core/jni/android_tracing_PerfettoDataSource.h
+++ b/core/jni/android_tracing_PerfettoDataSource.h
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "Perfetto"
-
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/Log.h>
#include <nativehelper/JNIHelp.h>
diff --git a/core/jni/android_tracing_PerfettoDataSourceInstance.h b/core/jni/android_tracing_PerfettoDataSourceInstance.h
index ebb5259..be71cbb 100644
--- a/core/jni/android_tracing_PerfettoDataSourceInstance.h
+++ b/core/jni/android_tracing_PerfettoDataSourceInstance.h
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "Perfetto"
-
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/Log.h>
#include <nativehelper/JNIHelp.h>
diff --git a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
index 54c4cd5..e0cc055 100644
--- a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
+++ b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
@@ -354,6 +354,18 @@
return result;
}
+static uid_t getSocketPeerUid(int socket, const std::function<void(const std::string&)>& fail_fn) {
+ struct ucred credentials;
+ socklen_t cred_size = sizeof credentials;
+ if (getsockopt(socket, SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1
+ || cred_size != sizeof credentials) {
+ fail_fn(CREATE_ERROR("Failed to get socket credentials, %s",
+ strerror(errno)));
+ }
+
+ return credentials.uid;
+}
+
// Read all lines from the current command into the buffer, and then reset the buffer, so
// we will start reading again at the beginning of the command, starting with the argument
// count. And we don't need access to the fd to do so.
@@ -413,19 +425,12 @@
fail_fn_z("Failed to retrieve session socket timeout");
}
- struct ucred credentials;
- socklen_t cred_size = sizeof credentials;
- if (getsockopt(n_buffer->getFd(), SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1
- || cred_size != sizeof credentials) {
- fail_fn_1(CREATE_ERROR("ForkRepeatedly failed to get initial credentials, %s",
- strerror(errno)));
+ uid_t peerUid = getSocketPeerUid(session_socket, fail_fn_1);
+ if (peerUid != static_cast<uid_t>(expected_uid)) {
+ return JNI_FALSE;
}
-
bool first_time = true;
do {
- if (credentials.uid != static_cast<uid_t>(expected_uid)) {
- return JNI_FALSE;
- }
n_buffer->readAllLines(first_time ? fail_fn_1 : fail_fn_n);
n_buffer->reset();
int pid = zygote::forkApp(env, /* no pipe FDs */ -1, -1, session_socket_fds,
@@ -453,6 +458,7 @@
}
}
for (;;) {
+ bool valid_session_socket = true;
// Clear buffer and get count from next command.
n_buffer->clear();
// Poll isn't strictly necessary for now. But without it, disconnect is hard to detect.
@@ -463,25 +469,50 @@
if ((fd_structs[SESSION_IDX].revents & POLLIN) != 0) {
if (n_buffer->getCount(fail_fn_z) != 0) {
break;
- } // else disconnected;
+ } else {
+ // Session socket was disconnected
+ valid_session_socket = false;
+ close(session_socket);
+ }
} else if (poll_res == 0 || (fd_structs[ZYGOTE_IDX].revents & POLLIN) == 0) {
fail_fn_z(
CREATE_ERROR("Poll returned with no descriptors ready! Poll returned %d", poll_res));
}
- // We've now seen either a disconnect or connect request.
- close(session_socket);
- int new_fd = TEMP_FAILURE_RETRY(accept(zygote_socket_fd, nullptr, nullptr));
+ int new_fd = -1;
+ do {
+ // We've now seen either a disconnect or connect request.
+ new_fd = TEMP_FAILURE_RETRY(accept(zygote_socket_fd, nullptr, nullptr));
+ if (new_fd == -1) {
+ fail_fn_z(CREATE_ERROR("Accept(%d) failed: %s", zygote_socket_fd, strerror(errno)));
+ }
+ uid_t newPeerUid = getSocketPeerUid(new_fd, fail_fn_1);
+ if (newPeerUid != static_cast<uid_t>(expected_uid)) {
+ ALOGW("Dropping new connection with a mismatched uid %d\n", newPeerUid);
+ close(new_fd);
+ new_fd = -1;
+ } else {
+ // If we still have a valid session socket, close it now
+ if (valid_session_socket) {
+ close(session_socket);
+ }
+ valid_session_socket = true;
+ }
+ } while (!valid_session_socket);
+
+ // At this point we either have a valid new connection (new_fd > 0), or
+ // an existing session socket we can poll on
if (new_fd == -1) {
- fail_fn_z(CREATE_ERROR("Accept(%d) failed: %s", zygote_socket_fd, strerror(errno)));
+ // The new connection wasn't valid, and we still have an old one; retry polling
+ continue;
}
if (new_fd != session_socket) {
- // Move new_fd back to the old value, so that we don't have to change Java-level data
- // structures to reflect a change. This implicitly closes the old one.
- if (TEMP_FAILURE_RETRY(dup2(new_fd, session_socket)) != session_socket) {
- fail_fn_z(CREATE_ERROR("Failed to move fd %d to %d: %s",
- new_fd, session_socket, strerror(errno)));
- }
- close(new_fd); // On Linux, fd is closed even if EINTR is returned.
+ // Move new_fd back to the old value, so that we don't have to change Java-level data
+ // structures to reflect a change. This implicitly closes the old one.
+ if (TEMP_FAILURE_RETRY(dup2(new_fd, session_socket)) != session_socket) {
+ fail_fn_z(CREATE_ERROR("Failed to move fd %d to %d: %s",
+ new_fd, session_socket, strerror(errno)));
+ }
+ close(new_fd); // On Linux, fd is closed even if EINTR is returned.
}
// If we ever return, we effectively reuse the old Java ZygoteConnection.
// None of its state needs to change.
@@ -493,13 +524,6 @@
fail_fn_z(CREATE_ERROR("Failed to set send timeout for socket %d: %s",
session_socket, strerror(errno)));
}
- if (getsockopt(session_socket, SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1) {
- fail_fn_z(CREATE_ERROR("ForkMany failed to get credentials: %s", strerror(errno)));
- }
- if (cred_size != sizeof credentials) {
- fail_fn_z(CREATE_ERROR("ForkMany credential size = %d, should be %d",
- cred_size, static_cast<int>(sizeof credentials)));
- }
}
first_time = false;
} while (n_buffer->isSimpleForkCommand(minUid, fail_fn_n));
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 75cfba0..d31baf3 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -144,6 +144,7 @@
}
repeated BroadcastSummary historical_broadcasts_summary = 6;
repeated BroadcastRecordProto pending_broadcasts = 7;
+ repeated BroadcastRecordProto frozen_broadcasts = 8;
}
message MemInfoDumpProto {
diff --git a/core/proto/android/view/inputmethod/inputconnection.proto b/core/proto/android/view/inputmethod/inputconnection.proto
index d1f257f..ff6871d 100644
--- a/core/proto/android/view/inputmethod/inputconnection.proto
+++ b/core/proto/android/view/inputmethod/inputconnection.proto
@@ -16,8 +16,6 @@
syntax = "proto2";
-import "frameworks/base/core/proto/android/privacy.proto";
-
package android.view.inputmethod;
option java_multiple_files = true;
@@ -26,8 +24,8 @@
* Represents a {@link android.view.inputmethod.InputConnection} object.
*/
message InputConnectionProto {
- optional string editable_text = 1 [(.android.privacy).dest = DEST_LOCAL];
- optional string selected_text = 2 [(.android.privacy).dest = DEST_LOCAL];
+ reserved 1; // string editable_text
+ reserved 2; // string selected_text
optional int32 selected_text_start = 3;
optional int32 selected_text_end = 4;
optional int32 cursor_caps_mode = 5;
@@ -50,18 +48,18 @@
message GetTextBeforeCursor {
optional int32 length = 1;
optional int32 flags = 2;
- optional string result = 3 [(.android.privacy).dest = DEST_LOCAL];
+ reserved 3; // string result
}
message GetTextAfterCursor {
optional int32 length = 1;
optional int32 flags = 2;
- optional string result = 3 [(.android.privacy).dest = DEST_LOCAL];
+ reserved 3; // string result = 3
}
message GetSelectedText {
optional int32 flags = 1;
- optional string result = 2 [(.android.privacy).dest = DEST_LOCAL];
+ reserved 2; // string result = 2
}
message GetSurroundingText {
@@ -71,7 +69,7 @@
optional SurroundingText result = 4;
message SurroundingText {
- optional string text = 1 [(.android.privacy).dest = DEST_LOCAL];
+ reserved 1; // string text = 1
optional int32 selection_start = 2;
optional int32 selection_end = 3;
optional int32 offset = 4;
@@ -86,7 +84,7 @@
message GetExtractedText {
optional ExtractedTextRequest request = 1;
optional int32 flags = 2;
- optional string result = 3 [(.android.privacy).dest = DEST_LOCAL];
+ reserved 3; // string result = 3
message ExtractedTextRequest {
optional int32 token = 1;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1acdc75..8ea742d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6916,6 +6916,12 @@
<permission android:name="android.permission.MANAGE_MEDIA_PROJECTION"
android:protectionLevel="signature" />
+ <!-- @hide @TestApi Allows an application to record sensitive content during media
+ projection. This is intended for on device screen recording system app.
+ @FlaggedApi("android.permission.flags.sensitive_notification_app_protection") -->
+ <permission android:name="android.permission.RECORD_SENSITIVE_CONTENT"
+ android:protectionLevel="signature"/>
+
<!-- @SystemApi Allows an application to read install sessions
@hide This is not a third-party API (intended for system apps). -->
<permission android:name="android.permission.READ_INSTALL_SESSIONS"
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 73877b8..09d23fa 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Jy kan boodskappe stuur en ontvang sonder ’n selfoon- of wi-fi-netwerk"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Maak Boodskappe oop"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Hoe dit werk"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index e513a07..6de4a20 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"ያለ ሞባይል ወይም የWi-Fi አውታረ መረብ መልዕክቶችን መላክ እና መቀበል ይችላሉ"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"መልዕክቶች ይክፈቱ"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"እንዴት እንደሚሠራ"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 95fa5db..3e98391 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -2397,4 +2397,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"يمكنك إرسال الرسائل واستلامها بدون شبكة الجوّال أو شبكة Wi-Fi."</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"فتح تطبيق \"الرسائل\""</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"طريقة العمل"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index 9fb7149..0e3773b 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"আপুনি ম’বাইল বা ৱাই-ফাই নেটৱৰ্কৰ জৰিয়তে পাঠ বাৰ্তা পঠিয়াব বা লাভ কৰিব পাৰে"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages খোলক"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ই কেনেকৈ কাম কৰে"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 5c3c652..98ec798 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Mobil və ya Wi-Fi şəbəkəsi olmadan mesaj göndərə və qəbul edə bilərsiniz"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Mesajı açın"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Haqqında"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index bda2a55..04b5a38 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -2394,4 +2394,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Možete da šaljete i primate poruke bez mobilne ili WiFi mreže"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Otvori Messages"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Princip rada"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 38b8e43..bb1b2d3 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -2395,4 +2395,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Вы можаце адпраўляць і атрымліваць паведамленні без доступу да мабільнай сеткі або Wi-Fi"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Адкрыць Паведамленні"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Як гэта працуе"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 77933ba..b03faf7 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Можете да изпращате и получавате съобщения без мобилна или Wi-Fi мрежа"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Отваряне на Messages"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Начин на работа"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index b7c7399..f637f5a 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"আপনি কোনও মেবাইল বা ওয়াই-ফাই নেটওয়ার্ক ছাড়াই মেসেজ পাঠাতে ও পেতে পারবেন"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages খুলুন"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"এটি কীভাবে কাজ করে"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 90af630..9700fe1 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -1989,7 +1989,7 @@
<string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Hitan slučaj"</string>
<string name="set_up_screen_lock_title" msgid="8346083801616474030">"Postavite zaključavanje ekrana"</string>
<string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Postavite zaključavanje ekrana"</string>
- <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Da koristite privatni prostor, postavite zaklj. ekr. na ur."</string>
+ <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Za upotrebu privatn. prostora postavite zaklj. ekr. na uređ."</string>
<string name="app_blocked_title" msgid="7353262160455028160">"Aplikacija nije dostupna"</string>
<string name="app_blocked_message" msgid="542972921087873023">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> trenutno nije dostupna."</string>
<string name="app_streaming_blocked_title" msgid="6090945835898766139">"Nedostupno: <xliff:g id="ACTIVITY">%1$s</xliff:g>"</string>
@@ -2394,4 +2394,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Možete slati i primati poruke bez mobilne ili WiFi mreže"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Otvorite Messages"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Kako ovo funkcionira"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index f20d334..e56b8bc 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -2394,4 +2394,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Pots enviar i rebre missatges sense una xarxa mòbil o Wi‑Fi"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Obre Missatges"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Com funciona"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index d603890..0c168e5 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -2395,4 +2395,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Zprávy můžete odesílat a přijímat bez mobilní sítě nebo sítě Wi-Fi"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Otevřít Zprávy"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Jak to funguje"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index fc73fa7..aa037c7 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Du kan sende og modtage beskeder uden et mobil- eller Wi-Fi-netværk"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Åbn Beskeder"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Sådan fungerer det"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 9faf515..9f1af24 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Du kannst Nachrichten ohne Mobilfunknetz oder WLAN senden und empfangen"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages öffnen"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"So funktionierts"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index eafaf32..bc4d929 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -186,8 +186,8 @@
<string name="contentServiceSyncNotificationTitle" msgid="5766411446676388623">"Αδυναμία συγχρονισμού"</string>
<string name="contentServiceTooManyDeletesNotificationDesc" msgid="4562226280528716090">"Επιχειρήθηκε η διαγραφή πάρα πολλών <xliff:g id="CONTENT_TYPE">%s</xliff:g>."</string>
<string name="low_memory" product="tablet" msgid="5557552311566179924">"Ο αποθηκευτικός χώρος του tablet είναι πλήρης. Διαγράψτε μερικά αρχεία για να δημιουργήσετε ελεύθερο χώρο."</string>
- <string name="low_memory" product="watch" msgid="3479447988234030194">"Ο αποθηκευτικός χώρος παρακολούθησης είναι πλήρης! Διαγράψτε μερικά αρχεία για να απελευθερώσετε χώρο."</string>
- <string name="low_memory" product="tv" msgid="6663680413790323318">"Ο αποθηκευτικός χώρος της συσκευής Android TV είναι πλήρης. Διαγράψτε μερικά αρχεία για να απελευθερώσετε χώρο."</string>
+ <string name="low_memory" product="watch" msgid="3479447988234030194">"Ο αποθηκευτικός χώρος παρακολούθησης είναι πλήρης! Διαγράψτε μερικά αρχεία για να αποδεσμεύσετε χώρο."</string>
+ <string name="low_memory" product="tv" msgid="6663680413790323318">"Ο αποθηκευτικός χώρος της συσκευής Android TV είναι πλήρης. Διαγράψτε μερικά αρχεία για να αποδεσμεύσετε χώρο."</string>
<string name="low_memory" product="default" msgid="2539532364144025569">"Ο αποθηκευτικός χώρος του τηλεφώνου είναι πλήρης. Διαγράψτε μερικά αρχεία για να δημιουργήσετε ελεύθερο χώρο."</string>
<string name="ssl_ca_cert_warning" msgid="7233573909730048571">"{count,plural, =1{Η αρχή έκδοσης πιστοποιητικών εγκαταστάθηκε}other{Οι αρχές έκδοσης πιστοποιητικών εγκαταστάθηκαν}}"</string>
<string name="ssl_ca_cert_noti_by_unknown" msgid="4961102218216815242">"Από ένα άγνωστο τρίτο μέρος"</string>
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Μπορείτε να στέλνετε και να λαμβάνετε μηνύματα χωρίς δίκτυο κινητής τηλεφωνίας ή Wi-Fi."</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Άνοιγμα Messages"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Πώς λειτουργεί"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 7dd6a5c..969bbbf 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"You can send and receive messages without a mobile or Wi-Fi network"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 58c015b..4148ad7 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -2393,4 +2393,5 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"You can send and receive messages without a mobile or Wi-Fi network"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string>
+ <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pending..."</string>
</resources>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index fd0cdd5..0a890b2 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"You can send and receive messages without a mobile or Wi-Fi network"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 3dfadb2..5ca5236 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"You can send and receive messages without a mobile or Wi-Fi network"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 6c6f1c9..edba901 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -2393,4 +2393,5 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"You can send and receive messages without a mobile or Wi-Fi network"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string>
+ <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pending..."</string>
</resources>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index d7af663..65e53db 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -1987,9 +1987,9 @@
<string name="work_mode_off_title" msgid="6367463960165135829">"¿Reanudar apps de trabajo?"</string>
<string name="work_mode_turn_on" msgid="5316648862401307800">"Reanudar"</string>
<string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Emergencia"</string>
- <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Configura bloqueo de pantalla"</string>
- <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Conf. un bloqueo de pantalla"</string>
- <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Para usar esp. privado, configura un bloqueo de pantalla"</string>
+ <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Configurar bloqueo de pantalla"</string>
+ <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Configurar bloqueo de pantalla"</string>
+ <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Para usar tu espacio privado, configura un bloqueo de pantalla"</string>
<string name="app_blocked_title" msgid="7353262160455028160">"La app no está disponible"</string>
<string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> no está disponible en este momento."</string>
<string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> no disponible"</string>
@@ -2394,4 +2394,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Puedes enviar y recibir mensajes incluso si no tienes conexión a una red móvil o Wi-Fi"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Abrir Mensajes"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cómo funciona"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 02e29c0..a0e4a51 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -2394,4 +2394,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Puedes enviar y recibir mensajes sin una red móvil o Wi-Fi"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Abre Mensajes"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cómo funciona"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 2fe8731..d6553ab 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Teil on võimalik sõnumeid saata ja vastu võtta ilma mobiilside- ja WiFi-võrguta"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Ava rakendus Messages"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Tööpõhimõtted"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index c28191a..bdf946e 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Mezuak bidal eta jaso ditzakezu sare mugikorrik edo wifi-sarerik gabe"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Ireki Mezuak"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Nola funtzionatzen du?"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 7155bf4..827ddaa 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"میتوانید بدون شبکه تلفن همراه یا Wi-Fi پیام ارسال و دریافت کنید"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"باز کردن «پیامها»"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"روش کار"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 11d5604..dca0e26 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Voit lähettää ja vastaanottaa viestejä ilman mobiili‑ tai Wi-Fi-verkkoa"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Avaa Messages"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Näin se toimii"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-fr-feminine/strings.xml b/core/res/res/values-fr-feminine/strings.xml
new file mode 100644
index 0000000..2ad85d1
--- /dev/null
+++ b/core/res/res/values-fr-feminine/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"lire les flux auxquels vous êtes abonnée"</string>
+</resources>
diff --git a/core/res/res/values-fr-masculine/strings.xml b/core/res/res/values-fr-masculine/strings.xml
new file mode 100644
index 0000000..744ef2b
--- /dev/null
+++ b/core/res/res/values-fr-masculine/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"lire les flux auxquels vous êtes abonné"</string>
+</resources>
diff --git a/core/res/res/values-fr-neuter/strings.xml b/core/res/res/values-fr-neuter/strings.xml
new file mode 100644
index 0000000..b4f4cc7
--- /dev/null
+++ b/core/res/res/values-fr-neuter/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"lire les flux auxquels vous êtes abonné·e"</string>
+</resources>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index bec2f1f..dc030bb 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -2394,4 +2394,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Vous pouvez envoyer et recevoir des messages sans avoir recours à un appareil mobile ou à un réseau Wi-Fi"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Ouvrir Messages"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Fonctionnement"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index d76b1ef..31754e4 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -2394,4 +2394,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Vous pouvez envoyer et recevoir des messages sans connexion au réseau mobile ou Wi-Fi"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Ouvrir Messages"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Fonctionnement"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 6b7507a..1b99b4b 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Podes enviar e recibir mensaxes sen unha rede de telefonía móbil ou wifi"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Abrir Mensaxes"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Como funciona?"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 9f4919d..57c09da 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"તમે મોબાઇલ અથવા વાઇ-ફાઇ નેટવર્ક વિના મેસેજ મોકલી અને પ્રાપ્ત કરી શકો છો"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ખોલો"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"તેની કામ કરવાની રીત"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 6e91d3a..6bba6b0 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"मोबाइल या वाई-फ़ाई नेटवर्क के बिना भी मैसेज भेजे और पाए जा सकते हैं"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ऐप्लिकेशन खोलें"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"यह सेटिंग कैसे काम करती है"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 4682f84..75ca762 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -2394,4 +2394,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Možete slati i primati poruke bez mobilne mreže ili Wi-Fi mreže"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Otvori Poruke"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Kako to funkcionira"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index bde17d0..c825bd7 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Küldhet és fogadhat üzeneteket mobil- és Wi-Fi-hálózat nélkül is"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"A Messages megnyitása"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Hogyan működik?"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 8caacd3..1745f27 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Դուք կարող եք ուղարկել և ստանալ հաղորդագրություններ՝ առանց բջջային կամ Wi-Fi կապի"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Բացել Messages-ը"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Ինչպես է դա աշխատում"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 2020a75..af1ec29 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Anda dapat mengirim dan menerima pesan tanpa jaringan seluler atau Wi-Fi"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Buka Message"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cara kerjanya"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index f25a4b4..d921828 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Þú getur sent og móttekið skilaboð án tengingar við farsímakerfi eða Wi-Fi"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Opna Messages"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Svona virkar þetta"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 06fa36f..86640f8 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -2394,4 +2394,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Puoi inviare e ricevere messaggi senza una rete mobile o Wi-Fi"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Apri Messaggi"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Come funziona"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 1a609b9..d0ad38c 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -2394,4 +2394,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"אפשר לשלוח ולקבל הודעות ללא רשת סלולרית או רשת Wi-Fi"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"לפתיחת Messages"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"איך זה עובד"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 688450e..f899084 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -2393,4 +2393,5 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"モバイル ネットワークや Wi-Fi ネットワークを使わずにメッセージを送受信できます"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"メッセージ アプリを開く"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"仕組み"</string>
+ <string name="unarchival_session_app_label" msgid="6811856981546348205">"保留中..."</string>
</resources>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index a1e489a..e4e6042 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"შეგიძლიათ გაგზავნოთ და მიიღოთ შეტყობინებები მობილური ან Wi-Fi ქსელის გარეშე"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages-ის გახსნა"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"მუშაობის პრინციპი"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 0bef068..e9d00c7 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Мобильдік не Wi-Fi желісіне қосылмастан хабар жібере аласыз және ала аласыз."</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages қолданбасын ашу"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Бұл қалай орындалады?"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 1f805bf..118e6e6 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"អ្នកអាចផ្ញើ និងទទួលសារដោយមិនប្រើបណ្តាញទូរសព្ទចល័ត ឬ Wi-Fi"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"បើកកម្មវិធី Messages"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"របៀបដែលវាដំណើរការ"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index c3fab52..4331819 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"ನೀವು ಮೊಬೈಲ್ ಅಥವಾ ವೈ-ಫೈ ನೆಟ್ವರ್ಕ್ ಇಲ್ಲದೆಯೇ ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸಬಹುದು ಮತ್ತು ಸ್ವೀಕರಿಸಬಹುದು"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ಅನ್ನು ತೆರೆಯಿರಿ"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ಇದು ಹೇಗೆ ಕೆಲಸ ಮಾಡುತ್ತದೆ"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 9b7556a..8975b43 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"모바일 또는 Wi-Fi 네트워크 없이 메시지를 주고 받을 수 있습니다"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"메시지 열기"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"작동 방식"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 71c128d..c8b5981 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Сиз мобилдик же Wi-Fi тармагы жок эле билдирүүлөрдү жөнөтүп, ала аласыз"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Жазышуулар колдонмосун ачуу"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Ал кантип иштейт"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 8c19e7b..06a572d 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"ທ່ານສາມາດສົ່ງ ແລະ ຮັບຂໍ້ຄວາມໂດຍບໍ່ຕ້ອງໃຊ້ເຄືອຂ່າຍມືຖື ຫຼື Wi-Fi"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"ເປີດ Messages"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ມັນເຮັດວຽກແນວໃດ"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index bf227d0..98bcca9 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -2395,4 +2395,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Galite siųsti ir gauti pranešimus be mobiliojo ryšio ar „Wi-Fi“ tinklo"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Atidaryti programą „Messages“"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Kaip tai veikia"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index a575e2a..6ed95d3 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -2394,4 +2394,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Varat sūtīt un saņemt ziņojumus bez mobilā vai Wi-Fi tīkla."</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Atvērt lietotni Ziņojumi"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Darbības principi"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-mcc334-mnc020-fr-feminine/strings.xml b/core/res/res/values-mcc334-mnc020-fr-feminine/strings.xml
new file mode 100644
index 0000000..ba278df
--- /dev/null
+++ b/core/res/res/values-mcc334-mnc020-fr-feminine/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed" msgid="3838388706348367865">"Vous n\'êtes pas autorisée à avoir plusieurs connexions PDN pour un APN donné -55-"</string>
+</resources>
diff --git a/core/res/res/values-mcc334-mnc020-fr-masculine/strings.xml b/core/res/res/values-mcc334-mnc020-fr-masculine/strings.xml
new file mode 100644
index 0000000..8227cd6
--- /dev/null
+++ b/core/res/res/values-mcc334-mnc020-fr-masculine/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed" msgid="3838388706348367865">"Vous n\'êtes pas autorisé à avoir plusieurs connexions PDN pour un APN donné -55-"</string>
+</resources>
diff --git a/core/res/res/values-mcc334-mnc020-fr-neuter/strings.xml b/core/res/res/values-mcc334-mnc020-fr-neuter/strings.xml
new file mode 100644
index 0000000..110d962
--- /dev/null
+++ b/core/res/res/values-mcc334-mnc020-fr-neuter/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed" msgid="3838388706348367865">"Vous n\'êtes pas autorisé·e à avoir plusieurs connexions PDN pour un APN donné -55-"</string>
+</resources>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 0ff49ed..3a60709 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Може да испраќате и примате пораки без мобилна или Wi-Fi мрежа"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Отворете ја Messages"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Дознајте како функционира"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 2648a38..55633dc 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -706,8 +706,8 @@
<string name="face_acquired_too_far" msgid="2922278214231064859">"ഫോൺ അടുത്തേക്ക് നീക്കുക"</string>
<string name="face_acquired_too_high" msgid="8278815780046368576">"ഫോൺ മുകളിലേക്ക് ഉയർത്തുക"</string>
<string name="face_acquired_too_low" msgid="4075391872960840081">"ഫോൺ കൂടുതൽ താഴേക്ക് നീക്കുക"</string>
- <string name="face_acquired_too_right" msgid="6245286514593540859">"ഫോൺ നിങ്ങളുടെ ഇടതുവശത്തേക്ക് നീക്കുക"</string>
- <string name="face_acquired_too_left" msgid="9201762240918405486">"ഫോൺ നിങ്ങളുടെ വലതുവശത്തേക്ക് നീക്കുക"</string>
+ <string name="face_acquired_too_right" msgid="6245286514593540859">"ഫോൺ ഇടതുവശത്തേക്ക് നീക്കുക"</string>
+ <string name="face_acquired_too_left" msgid="9201762240918405486">"ഫോൺ വലതുവശത്തേക്ക് നീക്കുക"</string>
<string name="face_acquired_poor_gaze" msgid="4427153558773628020">"നിങ്ങളുടെ ഉപകരണത്തിന് നേരെ കൂടുതൽ നന്നായി നോക്കുക."</string>
<string name="face_acquired_not_detected" msgid="1057966913397548150">"മുഖം കാണുന്നില്ല. ഫോൺ കണ്ണിന് നേരെ പിടിക്കുക."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"വളരെയധികം ചലനം. ഫോൺ അനക്കാതെ നേരെ പിടിക്കുക."</string>
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"മൊബൈലോ വൈഫൈ നെറ്റ്വർക്കോ ഇല്ലാതെ തന്നെ സന്ദേശങ്ങൾ അയയ്ക്കാനും സ്വീകരിക്കാനും നിങ്ങൾക്ക് കഴിയും"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages തുറക്കുക"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ഇത് പ്രവർത്തിക്കുന്നത് എങ്ങനെയാണ്"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index c7f8524..a9e1c149 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Та мобайл эсвэл Wi-Fi сүлжээгүйгээр мессеж илгээх болон хүлээн авах боломжтой"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Мессежийг нээх"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Энэ хэрхэн ажилладаг вэ?"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 0c625c7..c20bdb0 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"तुम्ही मोबाइल किंवा वाय-फाय नेटवर्कशिवाय मेसेज पाठवू आणि मिळवू शकता"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages उघडा"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ते कसे काम करते"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 9c81b79..9048ffc 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Anda boleh menghantar dan menerima mesej tanpa rangkaian mudah alih atau Wi-Fi"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Buka Messages"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cara ciri ini berfungsi"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 2d603bc..32aed95 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"မိုဘိုင်း (သို့) Wi-Fi ကွန်ရက်မရှိဘဲ မက်ဆေ့ဂျ်များကို ပို့နိုင်၊ လက်ခံနိုင်သည်"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ဖွင့်ရန်"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"အလုပ်လုပ်ပုံ"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 2ea9d40..41e217d 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Du kan sende og motta meldinger uten mobil- eller wifi-nettverk"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Åpne Meldinger"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Slik fungerer det"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index b7996f7..6f139cd 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"तपाईं मोबाइल वा Wi-Fi नेटवर्कविनै म्यासेज पठाउन र प्राप्त गर्न सक्नुहुन्छ"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages खोल्नुहोस्"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"यसले काम गर्ने तरिका"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 8b60b53..b17d7a8 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Je kunt berichten sturen en krijgen zonder een mobiel of wifi-netwerk"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Berichten openen"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Hoe het werkt"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index b3e62aa..ade7c82 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"ଏକ ମୋବାଇଲ କିମ୍ବା ୱାଇ-ଫାଇ ନେଟୱାର୍କ ବିନା ଆପଣ ମେସେଜ ପଠାଇପାରିବେ ଏବଂ ପାଇପାରିବେ"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ଖୋଲନ୍ତୁ"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ଏହା କିପରି କାମ କରେ"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 283813d..439190b 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -2393,4 +2393,5 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"ਤੁਸੀਂ ਮੋਬਾਈਲ ਜਾਂ ਵਾਈ-ਫਾਈ ਨੈੱਟਵਰਕ ਤੋਂ ਬਿਨਾਂ ਸੁਨੇਹੇ ਭੇਜ ਅਤੇ ਪ੍ਰਾਪਤ ਕਰ ਸਕਦੇ ਹੋ"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ਐਪ ਖੋਲ੍ਹੋ"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ਇਹ ਕਿਵੇਂ ਕੰਮ ਕਰਦਾ ਹੈ"</string>
+ <string name="unarchival_session_app_label" msgid="6811856981546348205">"ਵਿਚਾਰ-ਅਧੀਨ..."</string>
</resources>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 5e0e670..61814b7 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -2395,4 +2395,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Możesz wymieniać wiadomości bez dostępu do sieci komórkowej lub Wi-Fi"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Otwórz Wiadomości"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Jak to działa"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 3ccb86a..cf1a200 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -2394,4 +2394,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Você pode enviar e receber mensagens sem um dispositivo móvel ou uma rede Wi-Fi"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Abrir o app Mensagens"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Como funciona"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index f26eb78..9f6b733 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -2122,11 +2122,11 @@
<string name="notification_appops_camera_active" msgid="8177643089272352083">"Câmara"</string>
<string name="notification_appops_microphone_active" msgid="581333393214739332">"Microfone"</string>
<string name="notification_appops_overlay_active" msgid="5571732753262836481">"sobrepõe-se a outras aplicações no ecrã"</string>
- <string name="notification_feedback_indicator" msgid="663476517711323016">"Fornecer feedback"</string>
- <string name="notification_feedback_indicator_alerted" msgid="6552871804121942099">"Esta notificação foi promovida para Predefinida. Toque para fornecer feedback."</string>
- <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"Esta notificação foi despromovida para Silenciosa. Toque para fornecer feedback."</string>
- <string name="notification_feedback_indicator_promoted" msgid="9030204303764698640">"Esta notificação passou para uma classificação superior. Toque para fornecer feedback."</string>
- <string name="notification_feedback_indicator_demoted" msgid="8880309924296450875">"Esta notificação passou para uma classificação inferior. Toque para fornecer feedback."</string>
+ <string name="notification_feedback_indicator" msgid="663476517711323016">"Dar feedback"</string>
+ <string name="notification_feedback_indicator_alerted" msgid="6552871804121942099">"Esta notificação foi promovida para Predefinida. Toque para dar feedback."</string>
+ <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"Esta notificação foi despromovida para Silenciosa. Toque para dar feedback."</string>
+ <string name="notification_feedback_indicator_promoted" msgid="9030204303764698640">"Esta notificação passou para uma classificação superior. Toque para dar feedback."</string>
+ <string name="notification_feedback_indicator_demoted" msgid="8880309924296450875">"Esta notificação passou para uma classificação inferior. Toque para dar feedback."</string>
<string name="nas_upgrade_notification_title" msgid="8436359459300146555">"Notificações melhoradas"</string>
<string name="nas_upgrade_notification_content" msgid="5157550369837103337">"As ações e as respostas sugeridas são agora fornecidas por notificações melhoradas. As notificações adaptáveis do Android já não são suportadas."</string>
<string name="nas_upgrade_notification_enable_action" msgid="3046406808378726874">"OK"</string>
@@ -2394,4 +2394,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Pode enviar e receber mensagens sem uma rede móvel ou Wi-Fi"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Abre a app Mensagens"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Como funciona"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 3ccb86a..cf1a200 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -2394,4 +2394,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Você pode enviar e receber mensagens sem um dispositivo móvel ou uma rede Wi-Fi"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Abrir o app Mensagens"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Como funciona"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 049ef0c..38b6e53 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -2394,4 +2394,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Poți să trimiți și să primești mesaje fără o rețea mobilă sau Wi-Fi"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Deschide Mesaje"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cum funcționează"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 8abfb65..866f316 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -2395,4 +2395,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Вы можете отправлять и получать сообщения без доступа к мобильной сети или Wi-Fi."</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Открыть Сообщения"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Узнать принцип работы"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 5078ee0..3668de1e 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"ඔබට ජංගම හෝ Wi-Fi ජාලයක් නොමැතිව පණිවිඩ යැවීමට සහ ලැබීමට හැක"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages විවෘත කරන්න"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"එය ක්රියා කරන ආකාරය"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index a10cc48..1c642f6 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -2395,4 +2395,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Správy môžete odosielať a prijímať bez mobilnej siete či siete Wi-Fi"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Otvoriť Správy"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Ako to funguje"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 70eb803..0e72e0c 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -2395,4 +2395,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Sporočila SMS lahko pošiljate in prejemate brez mobilnega omrežja ali omrežja Wi-Fi"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Odpri Sporočila"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Kako deluje"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 61d815d..caa3409 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Mund të dërgosh dhe të marrësh mesazhe pa një rrjet celular apo rrjet Wi-Fi"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Hap \"Mesazhet\""</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Si funksionon"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index f0c8a20..75439d7 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -2394,4 +2394,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Можете да шаљете и примате поруке без мобилне или WiFi мреже"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Отвори Messages"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Принцип рада"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 79cdce4..be777f0 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Du kan skicka och ta emot meddelanden utan mobil- eller wifi-nätverk"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Öppna Messages"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Så fungerar det"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 4111f3a..65c6321 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Unaweza kutuma na kupokea ujumbe bila mtandao wa simu au Wi-Fi"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Fungua Programu ya Messages"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Utaratibu wake"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 3d33923..b3d16d7 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"மொபைல்/வைஃபை நெட்வொர்க் இல்லாமல் நீங்கள் மெசேஜ்களை அனுப்பலாம் பெறலாம்"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ஆப்ஸைத் திறக்கவும்"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"இது செயல்படும் விதம்"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 5887cd3..c8778a4 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"మీరు మొబైల్ లేదా Wi-Fi నెట్వర్క్ లేకుండా మెసేజ్లను పంపవచ్చు, స్వీకరించవచ్చు"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Messagesను తెరవండి"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ఇది ఎలా పని చేస్తుంది"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 51ff5ae..8a05689 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"คุณรับส่งข้อความผ่านดาวเทียมได้โดยไม่ต้องใช้เครือข่ายมือถือหรือ Wi-Fi"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"เปิด Messages"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"วิธีการทำงาน"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 43ce6bc..3b3797a 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Puwede kang magpadala at tumanggap ng mga mensahe nang walang mobile o Wi-Fi network"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Buksan ang Messages"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Paano ito gumagana"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 1df9b8d..70ea414 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Mobil veya kablosuz ağa bağlı olmadan mesaj alıp gönderebilirsiniz"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Mesajlar\'ı aç"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"İşleyiş şekli"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 903261c..9c7c8ef 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -2395,4 +2395,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Ви можете надсилати й отримувати повідомлення, не використовуючи Wi-Fi або мобільну мережу"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Відкрийте Повідомлення"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Як це працює"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index bed6cf8..e60299f 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"آپ موبائل یا Wi-Fi نیٹ ورک کے بغیر پیغامات بھیج اور موصول کر سکتے ہیں"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"پیغامات ایپ کو کھولیں"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"اس کے کام کرنے کا طریقہ"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 1316898..58e576b 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Mobil yoki Wi-Fi tarmoqsiz xabarlarni yuborishingiz va qabul qilishingiz mumkin"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Xabarlar ilovasini ochish"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Ishlash tartibi"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 4587a62..a8d8188 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Bạn có thể gửi và nhận tin nhắn mà không cần có mạng di động hoặc mạng Wi-Fi"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Mở ứng dụng Tin nhắn"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cách hoạt động"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-watch/colors.xml b/core/res/res/values-watch/colors.xml
index 0b00bd8..e2b7505 100644
--- a/core/res/res/values-watch/colors.xml
+++ b/core/res/res/values-watch/colors.xml
@@ -18,11 +18,26 @@
<resources>
<color name="system_error_light">#B3261E</color>
<color name="system_on_error_light">#FFFFFF</color>
- <color name="system_error_container_light">#F9DEDC</color>
+ <color name="system_error_container_light">#F7DCDA</color>
<color name="system_on_error_container_light">#410E0B</color>
- <color name="system_error_dark">#EC928E</color>
- <color name="system_on_error_dark">#410E0B</color>
- <color name="system_error_container_dark">#F2B8B5</color>
- <color name="system_on_error_container_dark">#601410</color>
+ <color name="system_error_dark">#F2B8B5</color>
+ <color name="system_on_error_dark">#601410</color>
+ <color name="system_error_container_dark">#FF8986</color>
+ <color name="system_on_error_container_dark">#410E0B</color>
+
+ <!-- With material deprecation of 'background' in favor of 'surface' we flatten these
+ on watches to match the black background requirements -->
+ <color name="system_surface_dark">#000000</color>
+ <color name="system_surface_dim_dark">#000000</color>
+ <color name="system_surface_bright_dark">#000000</color>
+
+ <!-- Wear flattens the typical 5 container layers to 3; container + high & low -->
+ <color name="system_surface_container_dark">#303030</color>
+ <color name="system_surface_variant_dark">#303030</color>
+ <color name="system_surface_container_high_dark">#474747</color>
+ <color name="system_surface_container_highest_dark">#474747</color>
+ <color name="system_surface_container_low_dark">#252626</color>
+ <color name="system_surface_container_lowest_dark">#252626</color>
+
</resources>
diff --git a/core/res/res/values-watch/themes_device_defaults.xml b/core/res/res/values-watch/themes_device_defaults.xml
index 6e804c0..9ad577a 100644
--- a/core/res/res/values-watch/themes_device_defaults.xml
+++ b/core/res/res/values-watch/themes_device_defaults.xml
@@ -181,19 +181,95 @@
<style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Material.Dialog.Alert">
<item name="android:windowFullscreen">true</item>
- <!-- Color palette Dark -->
+ <!-- Dialog attributes -->
+ <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
+ <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+ <!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
- <item name="colorForeground">@color/foreground_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+ <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
+ <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
+ <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
+ <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_dark_device_default</item>
+ <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_dark_device_default</item>
+ <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_dark_device_default</item>
+ <item name="colorSurface">@color/surface_dark</item>
+ <item name="colorSurfaceHighlight">@color/surface_highlight_dark</item>
+ <item name="colorSurfaceVariant">@color/surface_variant_dark</item>
+ <item name="colorSurfaceHeader">@color/surface_header_dark</item>
+ <item name="colorError">@color/error_color_device_default_dark</item>
<item name="colorBackground">@color/background_device_default_dark</item>
<item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
- <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item>
- <item name="colorButtonNormal">@color/button_normal_device_default_dark</item>
- <item name="colorError">@color/error_color_device_default_dark</item>
- <item name="disabledAlpha">@dimen/disabled_alpha_device_default</item>
- <item name="primaryContentAlpha">@dimen/primary_content_alpha_device_default</item>
- <item name="secondaryContentAlpha">@dimen/secondary_content_alpha_device_default</item>
+ <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
+ <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
+ <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
+ <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item>
+ <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item>
+ <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item>
+ <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+ <item name="colorForeground">@color/foreground_device_default_dark</item>
+ <item name="colorForegroundInverse">@color/foreground_device_default_light</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
+
+ <!-- Progress bar attributes -->
+ <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
+ <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item>
+
+ <!-- Toolbar attributes -->
+ <item name="toolbarStyle">@style/Widget.DeviceDefault.Toolbar</item>
+
+ <item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item>
+ <item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item>
+ <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_dark</item>
+ <item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item>
+ <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_dark</item>
+ <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_dark</item>
+ <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_dark</item>
+ <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_dark</item>
+ <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
+ <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item>
+ <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
+ <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+ <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
+ <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
+ <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
+ <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item>
+ <item name="materialColorErrorContainer">@color/system_error_container_dark</item>
+ <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
+ <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+ <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
+ <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
+ <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item>
+ <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item>
+ <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
+ <item name="materialColorPrimaryContainer">@color/system_primary_container_dark</item>
+ <item name="materialColorOnBackground">@color/system_on_background_dark</item>
+ <item name="materialColorPrimaryFixed">@color/system_primary_fixed</item>
+ <item name="materialColorOnSecondary">@color/system_on_secondary_dark</item>
+ <item name="materialColorOnTertiary">@color/system_on_tertiary_dark</item>
+ <item name="materialColorSurfaceDim">@color/system_surface_dim_dark</item>
+ <item name="materialColorSurfaceBright">@color/system_surface_bright_dark</item>
+ <item name="materialColorOnError">@color/system_on_error_dark</item>
+ <item name="materialColorSurface">@color/system_surface_dark</item>
+ <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_dark</item>
+ <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
+ <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
+ <item name="materialColorOutline">@color/system_outline_dark</item>
+ <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
+ <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
+ <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
+ <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
+ <item name="materialColorPrimary">@color/system_primary_dark</item>
+ <item name="materialColorSecondary">@color/system_secondary_dark</item>
+ <item name="materialColorTertiary">@color/system_tertiary_dark</item>
+ <item name="materialColorError">@color/system_error_dark</item>
</style>
<!-- DeviceDefault theme for a window that should look like the Settings app. -->
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index c58c0de..b0f052e 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"您无需使用移动网络或 WLAN 网络便能收发消息"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"打开“信息”应用"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"运作方式"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index b72569a..0bf9079 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"你可在沒有流動/Wi-Fi 網絡的情況下收發訊息"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"開啟「訊息」"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"運作方式"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 3c7619c..c15262d 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -492,8 +492,8 @@
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"允許應用程式存取額外位置資訊提供者指令。這項設定可能會造成應用程式干擾 GPS 或其他位置資訊來源的運作。"</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"僅可在前景中取得精確位置"</string>
<string name="permdesc_accessFineLocation" msgid="6732174080240016335">"只有在你使用時,這個應用程式才能透過定位服務取得你的精確位置。你必須在裝置上開啟定位服務,這個應用程式才能取得位置資訊。這麼做可能會增加電池用量。"</string>
- <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"僅可在前景中取得概略位置"</string>
- <string name="permdesc_accessCoarseLocation" msgid="778521847873199160">"只有在你使用時,這個應用程式才能透過定位服務取得你的概略位置。你必須在裝置上開啟定位服務,這個應用程式才能取得位置資訊。"</string>
+ <string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"僅可在前景中取得大概位置"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="778521847873199160">"只有在你使用時,這個應用程式才能透過定位服務取得你的大概位置。你必須在裝置上開啟定位服務,這個應用程式才能取得位置資訊。"</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"在背景存取位置資訊"</string>
<string name="permdesc_accessBackgroundLocation" msgid="8264885066095638105">"這個應用程式隨時都能取得位置資訊 (包括未使用應用程式時)。"</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"變更音訊設定"</string>
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"你可以收發訊息,沒有行動/Wi-Fi 網路也無妨"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"開啟「訊息」應用程式"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"運作方式"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index c9dd914..91050bf 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -2393,4 +2393,6 @@
<string name="satellite_notification_summary" msgid="5207364139430767162">"Ungathumela futhi wamukele imilayezo ngaphandle kwenethiwekhi yeselula noma ye-Wi-Fi"</string>
<string name="satellite_notification_open_message" msgid="4149234979688273729">"Vula Imilayezo"</string>
<string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Indlela esebenza ngayo"</string>
+ <!-- no translation found for unarchival_session_app_label (6811856981546348205) -->
+ <skip />
</resources>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index d89f236..5e900f7 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -3287,10 +3287,9 @@
-->
<attr name="enableOnBackInvokedCallback" format="boolean"/>
- <!-- Specifies permissions necessary to launch this activity via
- {@link android.content.Context#startActivity} when passing content URIs. The default
- value is {@code none}, meaning no specific permissions are required. Setting this
- attribute restricts activity invocation based on the invoker's permissions. If the
+ <!-- Specifies permissions necessary to launch this activity when passing content URIs. The
+ default value is {@code none}, meaning no specific permissions are required. Setting
+ this attribute restricts activity invocation based on the invoker's permissions. If the
invoker doesn't have the required permissions, the activity start will be denied via a
{@link java.lang.SecurityException}.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 9d902c9..f59c099 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1608,7 +1608,10 @@
<fraction name="config_autoBrightnessAdjustmentMaxGamma">300%</fraction>
<!-- If we allow automatic adjustment of screen brightness while dozing, how many times we want
- to reduce it to preserve the battery. Value of 100% means no scaling. -->
+ to reduce it to preserve the battery. Value of 100% means no scaling. Not used if there is
+ a designated auto-brightness doze mapping defined in Display Device Config.
+ Also used to scale the brightness for the doze mode when auto-brightness is disabled if
+ there is an offload session present. -->
<fraction name="config_screenAutoBrightnessDozeScaleFactor">100%</fraction>
<!-- When the screen is turned on, the previous estimate of the ambient light level at the time
@@ -3129,6 +3132,10 @@
-->
<bool name="config_enableWifiDisplay">false</bool>
+ <!-- Whether the default HDR conversion mode should be passthrough instead of system.
+ -->
+ <bool name="config_enableDefaultHdrConversionPassthrough">false</bool>
+
<!-- When true, local displays that do not contain any of their own content will automatically
mirror the content of the default display. -->
<bool name="config_localDisplaysMirrorContent">true</bool>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 6e56fe2..7c9f2ef 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -339,4 +339,17 @@
<integer name="config_metrics_pull_cooldown_millis">82800000</integer>
<java-symbol type="integer" name="config_metrics_pull_cooldown_millis" />
+ <!-- The network capabilities that would be forced marked as cellular transport regardless it's
+ on cellular or satellite-->
+ <string-array name="config_force_cellular_transport_capabilities">
+ <!-- Added the following three capabilities for now. For the long term solution, the client
+ requests satellite network should really include TRANSPORT_SATELLITE in the network
+ request. With the following workaround, the clients can continue request network with
+ the following capabilities with TRANSPORT_CELLULAR. The network with one of the
+ following capabilities would also be marked as cellular. -->
+ <item>ims</item>
+ <item>eims</item>
+ <item>xcap</item>
+ </string-array>
+ <java-symbol type="array" name="config_force_cellular_transport_capabilities" />
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 238772f..c7e08e2 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5452,7 +5452,7 @@
<string name="set_up_screen_lock_title">Set a screen lock</string>
<!-- Action label for the dialog prompting the user to set up a screen lock [CHAR LIMIT=30] -->
<string name="set_up_screen_lock_action_label">Set screen lock</string>
- <!-- Message shown in the dialog prompting the user to set up a screen lock to access private space [CHAR LIMIT=30] -->
+ <!-- Message shown in the dialog prompting the user to set up a screen lock to access private space [CHAR LIMIT=120] -->
<string name="private_space_set_up_screen_lock_message">To use your private space, set a screen lock on this device</string>
<!-- Title of the dialog that is shown when the user tries to launch a blocked application [CHAR LIMIT=50] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4b71654..c603fa7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -348,6 +348,7 @@
<java-symbol type="bool" name="config_enableScreenshotChord" />
<java-symbol type="bool" name="config_fold_lock_behavior" />
<java-symbol type="bool" name="config_enableWifiDisplay" />
+ <java-symbol type="bool" name="config_enableDefaultHdrConversionPassthrough" />
<java-symbol type="bool" name="config_allowAnimationsInLowPowerMode" />
<java-symbol type="bool" name="config_useDevInputEventForAudioJack" />
<java-symbol type="bool" name="config_safe_media_volume_enabled" />
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
index a034653..10ac05d 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
@@ -88,13 +88,6 @@
}
@Test
- public void setInternalHalCallback_callbackSetInHal() throws Exception {
- mRadioModule.setInternalHalCallback();
-
- verify(mBroadcastRadioMock).setTunerCallback(any());
- }
-
- @Test
public void getImage_withValidIdFromRadioModule() {
int imageId = 1;
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 262f167..755bcdb 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
@@ -192,66 +192,6 @@
mHalTunerCallback = (ITunerCallback) invocation.getArguments()[0];
return null;
}).when(mBroadcastRadioMock).setTunerCallback(any());
- mRadioModule.setInternalHalCallback();
-
- doAnswer(invocation -> {
- android.hardware.broadcastradio.ProgramSelector halSel =
- (android.hardware.broadcastradio.ProgramSelector) invocation.getArguments()[0];
- mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(halSel, SIGNAL_QUALITY);
- if (halSel.primaryId.type != IdentifierType.AMFM_FREQUENCY_KHZ) {
- throw new ServiceSpecificException(Result.NOT_SUPPORTED);
- }
- mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
- return Result.OK;
- }).when(mBroadcastRadioMock).tune(any());
-
- doAnswer(invocation -> {
- if ((boolean) invocation.getArguments()[0]) {
- mHalCurrentInfo.selector.primaryId.value += AM_FM_FREQUENCY_SPACING;
- } else {
- mHalCurrentInfo.selector.primaryId.value -= AM_FM_FREQUENCY_SPACING;
- }
- mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
- mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
- mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
- return Result.OK;
- }).when(mBroadcastRadioMock).step(anyBoolean());
-
- doAnswer(invocation -> {
- if (mHalCurrentInfo == null) {
- android.hardware.broadcastradio.ProgramSelector placeHolderSelector =
- AidlTestUtils.makeHalFmSelector(/* freq= */ 97300);
-
- mHalTunerCallback.onTuneFailed(Result.TIMEOUT, placeHolderSelector);
- return Result.OK;
- }
- mHalCurrentInfo.selector.primaryId.value = getSeekFrequency(
- mHalCurrentInfo.selector.primaryId.value,
- !(boolean) invocation.getArguments()[0]);
- mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
- mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
- mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
- return Result.OK;
- }).when(mBroadcastRadioMock).seek(anyBoolean(), anyBoolean());
-
- doReturn(null).when(mBroadcastRadioMock).getImage(anyInt());
-
- doAnswer(invocation -> {
- int configFlag = (int) invocation.getArguments()[0];
- if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
- throw new ServiceSpecificException(Result.NOT_SUPPORTED);
- }
- return mHalConfigMap.getOrDefault(configFlag, false);
- }).when(mBroadcastRadioMock).isConfigFlagSet(anyInt());
-
- doAnswer(invocation -> {
- int configFlag = (int) invocation.getArguments()[0];
- if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
- throw new ServiceSpecificException(Result.NOT_SUPPORTED);
- }
- mHalConfigMap.put(configFlag, (boolean) invocation.getArguments()[1]);
- return null;
- }).when(mBroadcastRadioMock).setConfigFlag(anyInt(), anyBoolean());
}
@After
@@ -330,6 +270,7 @@
expect.withMessage("Close state of broadcast radio service session")
.that(mTunerSessions[0].isClosed()).isTrue();
+ verify(mBroadcastRadioMock).unsetTunerCallback();
}
@Test
@@ -351,6 +292,7 @@
.that(mTunerSessions[index].isClosed()).isFalse();
}
}
+ verify(mBroadcastRadioMock, never()).unsetTunerCallback();
}
@Test
@@ -378,6 +320,7 @@
expect.withMessage("Close state of broadcast radio service session of index %s", index)
.that(mTunerSessions[index].isClosed()).isTrue();
}
+ verify(mBroadcastRadioMock).unsetTunerCallback();
}
@Test
@@ -1295,6 +1238,71 @@
mAidlTunerCallbackMocks[index] = mock(android.hardware.radio.ITunerCallback.class);
mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index]);
}
+ setupMockedHalTunerSession();
+ }
+
+ private void setupMockedHalTunerSession() throws Exception {
+ expect.withMessage("Registered HAL tuner callback").that(mHalTunerCallback)
+ .isNotNull();
+
+ doAnswer(invocation -> {
+ android.hardware.broadcastradio.ProgramSelector halSel =
+ (android.hardware.broadcastradio.ProgramSelector) invocation.getArguments()[0];
+ mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(halSel, SIGNAL_QUALITY);
+ if (halSel.primaryId.type != IdentifierType.AMFM_FREQUENCY_KHZ) {
+ throw new ServiceSpecificException(Result.NOT_SUPPORTED);
+ }
+ mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+ return Result.OK;
+ }).when(mBroadcastRadioMock).tune(any());
+
+ doAnswer(invocation -> {
+ if ((boolean) invocation.getArguments()[0]) {
+ mHalCurrentInfo.selector.primaryId.value += AM_FM_FREQUENCY_SPACING;
+ } else {
+ mHalCurrentInfo.selector.primaryId.value -= AM_FM_FREQUENCY_SPACING;
+ }
+ mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+ mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+ mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+ return Result.OK;
+ }).when(mBroadcastRadioMock).step(anyBoolean());
+
+ doAnswer(invocation -> {
+ if (mHalCurrentInfo == null) {
+ android.hardware.broadcastradio.ProgramSelector placeHolderSelector =
+ AidlTestUtils.makeHalFmSelector(/* freq= */ 97300);
+
+ mHalTunerCallback.onTuneFailed(Result.TIMEOUT, placeHolderSelector);
+ return Result.OK;
+ }
+ mHalCurrentInfo.selector.primaryId.value = getSeekFrequency(
+ mHalCurrentInfo.selector.primaryId.value,
+ !(boolean) invocation.getArguments()[0]);
+ mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+ mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
+ mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
+ return Result.OK;
+ }).when(mBroadcastRadioMock).seek(anyBoolean(), anyBoolean());
+
+ doReturn(null).when(mBroadcastRadioMock).getImage(anyInt());
+
+ doAnswer(invocation -> {
+ int configFlag = (int) invocation.getArguments()[0];
+ if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
+ throw new ServiceSpecificException(Result.NOT_SUPPORTED);
+ }
+ return mHalConfigMap.getOrDefault(configFlag, false);
+ }).when(mBroadcastRadioMock).isConfigFlagSet(anyInt());
+
+ doAnswer(invocation -> {
+ int configFlag = (int) invocation.getArguments()[0];
+ if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
+ throw new ServiceSpecificException(Result.NOT_SUPPORTED);
+ }
+ mHalConfigMap.put(configFlag, (boolean) invocation.getArguments()[1]);
+ return null;
+ }).when(mBroadcastRadioMock).setConfigFlag(anyInt(), anyBoolean());
}
private long getSeekFrequency(long currentFrequency, boolean seekDown) {
diff --git a/core/tests/PlatformCompatFramework/Android.bp b/core/tests/PlatformCompatFramework/Android.bp
index 95e23ad..2621d28 100644
--- a/core/tests/PlatformCompatFramework/Android.bp
+++ b/core/tests/PlatformCompatFramework/Android.bp
@@ -18,6 +18,7 @@
static_libs: [
"junit",
"androidx.test.rules",
+ "flag-junit",
],
platform_apis: true,
}
diff --git a/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java b/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java
index a052543..12a42f9 100644
--- a/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java
+++ b/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java
@@ -19,9 +19,17 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import com.android.internal.compat.flags.Flags;
+
+import org.junit.Rule;
import org.junit.Test;
public class ChangeReporterTest {
+
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Test
public void testStatsLogOnce() {
ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
@@ -63,7 +71,7 @@
ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
int myUid = 1022, otherUid = 1023;
long myChangeId = 500L, otherChangeId = 600L;
- int myState = ChangeReporter.STATE_ENABLED, otherState = ChangeReporter.STATE_DISABLED;
+ int myState = ChangeReporter.STATE_ENABLED, otherState = ChangeReporter.STATE_LOGGED;
assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myState));
reporter.reportChange(myUid, myChangeId, myState);
@@ -112,4 +120,80 @@
reporter.stopDebugLogAll();
assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myState));
}
+
+ @Test
+ public void testDebugLogWithFlagOnAndOldSdk() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_SKIP_OLD_AND_DISABLED_COMPAT_LOGGING);
+ ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
+ int myUid = 1022;
+ long myChangeId = 500L;
+ int myEnabledState = ChangeReporter.STATE_ENABLED;
+ int myDisabledState = ChangeReporter.STATE_DISABLED;
+
+ // Report will not log if target sdk is before the previous version.
+ assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, false));
+
+ reporter.resetReportedChanges(myUid);
+
+ // Report will be logged if target sdk is the latest version.
+ assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, true));
+
+ reporter.resetReportedChanges(myUid);
+
+ // If the report is disabled, the sdk version shouldn't matter.
+ assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myDisabledState, true));
+ }
+
+ @Test
+ public void testDebugLogWithFlagOnAndDisabledChange() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_SKIP_OLD_AND_DISABLED_COMPAT_LOGGING);
+ ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
+ int myUid = 1022;
+ long myChangeId = 500L;
+ int myEnabledState = ChangeReporter.STATE_ENABLED;
+ int myDisabledState = ChangeReporter.STATE_DISABLED;
+
+ // Report will not log if the change is disabled.
+ assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myDisabledState, true));
+
+ reporter.resetReportedChanges(myUid);
+
+ // Report will be logged if the change is enabled.
+ assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, true));
+
+ reporter.resetReportedChanges(myUid);
+
+ // If the report is not the latest version, the disabled state doesn't matter.
+ assertFalse(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, false));
+ }
+
+ @Test
+ public void testDebugLogWithFlagOff() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_SKIP_OLD_AND_DISABLED_COMPAT_LOGGING);
+ ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
+ int myUid = 1022;
+ long myChangeId = 500L;
+ int myEnabledState = ChangeReporter.STATE_ENABLED;
+ int myDisabledState = ChangeReporter.STATE_DISABLED;
+
+ // Report will be logged even if the change is not the latest sdk but the flag is off.
+ assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, false));
+
+ reporter.resetReportedChanges(myUid);
+
+ // Report will be logged if the change is enabled and the latest sdk but the flag is off.
+ assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myEnabledState, true));
+
+ reporter.resetReportedChanges(myUid);
+
+ // Report will be logged if the change is disabled and the latest sdk but the flag is
+ // off.
+ assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myDisabledState, true));
+
+ reporter.resetReportedChanges(myUid);
+
+ // Report will be logged if the change is disabled and not the latest sdk but the flag is
+ // off.
+ assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myDisabledState, false));
+ }
}
diff --git a/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
index 73d7fe9..544ca56 100644
--- a/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
+++ b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
@@ -220,6 +220,36 @@
assertEquals(0, out.toByteArray().length);
}
+ public void testDisassociationCleanup() throws InterruptedException {
+ // Create a new association
+ final int associationId = createAssociation();
+
+ // Subscribe to transport updates for new association
+ final CountDownLatch attached = new CountDownLatch(1);
+ final CountDownLatch detached = new CountDownLatch(1);
+ mCdm.addOnTransportsChangedListener(Runnable::run, associations -> {
+ if (associations.stream()
+ .anyMatch(association -> associationId == association.getId())) {
+ attached.countDown();
+ } else if (attached.getCount() == 0) {
+ detached.countDown();
+ }
+ });
+
+ final ByteArrayInputStream in = new ByteArrayInputStream(new byte[0]);
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ mCdm.attachSystemDataTransport(associationId, in, out);
+
+ // Assert that the transport is attached
+ assertTrue(attached.await(1, TimeUnit.SECONDS));
+
+ // When CDM disassociates, any transport attached to that associated device should detach
+ mCdm.disassociate(associationId);
+
+ // Assert that the transport is detached
+ assertTrue(detached.await(1, TimeUnit.SECONDS));
+ }
+
public static byte[] concat(byte[]... blobs) {
int length = 0;
for (byte[] blob : blobs) {
@@ -250,12 +280,14 @@
}
private int createAssociation() {
+ List<AssociationInfo> before = mCdm.getMyAssociations();
getInstrumentation().getUiAutomation().executeShellCommand("cmd companiondevice associate "
+ mContext.getUserId() + " " + mContext.getPackageName() + " AA:BB:CC:DD:EE:FF");
List<AssociationInfo> infos;
for (int i = 0; i < 100; i++) {
infos = mCdm.getMyAssociations();
- if (!infos.isEmpty()) {
+ if (infos.size() != before.size()) {
+ infos.removeAll(before);
return infos.get(0).getId();
} else {
SystemClock.sleep(100);
diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
index 5ac99db..89c2b3c 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
@@ -255,7 +255,7 @@
assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor());
assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor());
assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor());
- assertEquals(td1.getStatusBarAppearance(), td2.getStatusBarAppearance());
+ assertEquals(td1.getSystemBarsAppearance(), td2.getSystemBarsAppearance());
assertEquals(td1.getResizeMode(), td2.getResizeMode());
assertEquals(td1.getMinWidth(), td2.getMinWidth());
assertEquals(td1.getMinHeight(), td2.getMinHeight());
diff --git a/core/tests/coretests/src/android/app/assist/AssistStructureTest.java b/core/tests/coretests/src/android/app/assist/AssistStructureTest.java
index abeb08c..1f2788c 100644
--- a/core/tests/coretests/src/android/app/assist/AssistStructureTest.java
+++ b/core/tests/coretests/src/android/app/assist/AssistStructureTest.java
@@ -266,8 +266,8 @@
assertThat(view.getViewRootImpl()).isNotNull();
ViewNodeBuilder viewStructure = new ViewNodeBuilder();
viewStructure.setAutofillId(view.getAutofillId());
- viewStructure.setCredentialManagerRequest(view.getCredentialManagerRequest(),
- view.getCredentialManagerCallback());
+ viewStructure.setPendingCredentialRequest(view.getPendingCredentialRequest(),
+ view.getPendingCredentialCallback());
view.onProvideAutofillStructure(viewStructure, /* flags= */ 0);
ViewNodeParcelable viewNodeParcelable = new ViewNodeParcelable(viewStructure.getViewNode());
@@ -289,17 +289,20 @@
assertThat(view.getViewRootImpl()).isNotNull();
ViewNodeBuilder viewStructure = new ViewNodeBuilder();
- viewStructure.setCredentialManagerRequest(view.getCredentialManagerRequest(),
- view.getCredentialManagerCallback());
+ if (view.getPendingCredentialRequest() != null
+ && view.getPendingCredentialCallback() != null) {
+ viewStructure.setPendingCredentialRequest(view.getPendingCredentialRequest(),
+ view.getPendingCredentialCallback());
+ }
- assertEquals(viewStructure.getCredentialManagerRequest(), GET_CREDENTIAL_REQUEST);
- assertEquals(viewStructure.getCredentialManagerCallback(),
+ assertEquals(viewStructure.getPendingCredentialRequest(), GET_CREDENTIAL_REQUEST);
+ assertEquals(viewStructure.getPendingCredentialCallback(),
GET_CREDENTIAL_REQUEST_CALLBACK);
viewStructure.clearCredentialManagerRequest();
- assertNull(viewStructure.getCredentialManagerRequest());
- assertNull(viewStructure.getCredentialManagerCallback());
+ assertNull(viewStructure.getPendingCredentialRequest());
+ assertNull(viewStructure.getPendingCredentialCallback());
}
@Test
@@ -386,14 +389,14 @@
EditText view = new EditText(mContext);
view.setText("Big Hint in Little View");
view.setAutofillHints(BIG_STRING);
- view.setCredentialManagerRequest(GET_CREDENTIAL_REQUEST, GET_CREDENTIAL_REQUEST_CALLBACK);
+ view.setPendingCredentialRequest(GET_CREDENTIAL_REQUEST, GET_CREDENTIAL_REQUEST_CALLBACK);
return view;
}
private EditText newCredentialView() {
EditText view = new EditText(mContext);
view.setText("Credential Request");
- view.setCredentialManagerRequest(GET_CREDENTIAL_REQUEST, GET_CREDENTIAL_REQUEST_CALLBACK);
+ view.setPendingCredentialRequest(GET_CREDENTIAL_REQUEST, GET_CREDENTIAL_REQUEST_CALLBACK);
return view;
}
@@ -421,8 +424,8 @@
assertThat(view.getAutofillId()).isNotNull();
assertThat(view.getText().toString()).isEqualTo("Big Hint in Little View");
- assertThat(view.getCredentialManagerRequest()).isEqualTo(GET_CREDENTIAL_REQUEST);
- assertThat(view.getCredentialManagerCallback()).isEqualTo(GET_CREDENTIAL_REQUEST_CALLBACK);
+ assertThat(view.getPendingCredentialRequest()).isEqualTo(GET_CREDENTIAL_REQUEST);
+ assertThat(view.getPendingCredentialCallback()).isEqualTo(GET_CREDENTIAL_REQUEST_CALLBACK);
}
/**
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
index e118c98d..2905a5a 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
@@ -31,6 +31,7 @@
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.test.AndroidTestCase;
import android.util.Log;
+import android.util.Printer;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -46,12 +47,15 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Vector;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Phaser;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -403,4 +407,138 @@
}
assertFalse(allowed);
}
+
+ /** Dumpsys information about a single database. */
+
+ /**
+ * Collect and parse dumpsys output. This is not a full parser. It is only enough to support
+ * the unit tests.
+ */
+ private static class Dumpsys {
+ // Regular expressions for parsing the output. Reportedly, regular expressions are
+ // expensive, so these are created only if a dumpsys object is created.
+ private static final Object sLock = new Object();
+ static Pattern mPool;
+ static Pattern mConnection;
+ static Pattern mEntry;
+ static Pattern mSingleWord;
+ static Pattern mNone;
+
+ // The raw strings read from dumpsys. Once loaded, this list never changes.
+ final ArrayList<String> mRaw = new ArrayList<>();
+
+ // Parsed dumpsys. This contains only the bits that are being tested.
+ static class Connection {
+ ArrayList<String> mRecent = new ArrayList<>();
+ ArrayList<String> mLong = new ArrayList<>();
+ }
+ static class Database {
+ String mPath;
+ ArrayList<Connection> mConnection = new ArrayList<>();
+ }
+ ArrayList<Database> mDatabase;
+ ArrayList<String> mConcurrent;
+
+ Dumpsys() {
+ SQLiteDebug.dump(
+ new Printer() { public void println(String x) { mRaw.add(x); } },
+ new String[0]);
+ parse();
+ }
+
+ /** Parse the raw text. Return true if no errors were detected. */
+ boolean parse() {
+ initialize();
+
+ // Reset the parsed information. This method may be called repeatedly.
+ mDatabase = new ArrayList<>();
+ mConcurrent = new ArrayList<>();
+
+ Database current = null;
+ Connection connection = null;
+ Matcher matcher;
+ for (int i = 0; i < mRaw.size(); i++) {
+ final String line = mRaw.get(i);
+ matcher = mPool.matcher(line);
+ if (matcher.lookingAt()) {
+ current = new Database();
+ mDatabase.add(current);
+ current.mPath = matcher.group(1);
+ continue;
+ }
+ matcher = mConnection.matcher(line);
+ if (matcher.lookingAt()) {
+ connection = new Connection();
+ current.mConnection.add(connection);
+ continue;
+ }
+
+ if (line.contains("Most recently executed operations")) {
+ i += readTable(connection.mRecent, i, mEntry);
+ continue;
+ }
+
+ if (line.contains("Operations exceeding 2000ms")) {
+ i += readTable(connection.mLong, i, mEntry);
+ continue;
+ }
+ if (line.contains("Concurrently opened database files")) {
+ i += readTable(mConcurrent, i, mSingleWord);
+ continue;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Read a series of lines following a header. Return the number of lines read. The input
+ * line number is the number of the header.
+ */
+ private int readTable(List<String> s, int header, Pattern p) {
+ // Special case: if the first line is "<none>" then there are no more lines to the
+ // table.
+ if (lookingAt(header+1, mNone)) return 1;
+
+ int i;
+ for (i = header + 1; i < mRaw.size() && lookingAt(i, p); i++) {
+ s.add(mRaw.get(i).trim());
+ }
+ return i - header;
+ }
+
+ /** Return true if the n'th raw line matches the pattern. */
+ boolean lookingAt(int n, Pattern p) {
+ return p.matcher(mRaw.get(n)).lookingAt();
+ }
+
+ /** Compile the regular expressions the first time. */
+ private static void initialize() {
+ synchronized (sLock) {
+ if (mPool != null) return;
+ mPool = Pattern.compile("Connection pool for (\\S+):");
+ mConnection = Pattern.compile("\\s+Connection #(\\d+):");
+ mEntry = Pattern.compile("\\s+(\\d+): ");
+ mSingleWord = Pattern.compile(" (\\S+)$");
+ mNone = Pattern.compile("\\s+<none>$");
+ }
+ }
+ }
+
+ @Test
+ public void testDumpsys() throws Exception {
+ Dumpsys dumpsys = new Dumpsys();
+
+ assertEquals(1, dumpsys.mDatabase.size());
+ // Note: cannot test mConcurrent because that attribute is not hermitic with respect to
+ // the tests.
+
+ Dumpsys.Database db = dumpsys.mDatabase.get(0);
+
+ // Work with normalized paths.
+ String wantPath = mDatabaseFile.toPath().toRealPath().toString();
+ String realPath = new File(db.mPath).toPath().toRealPath().toString();
+ assertEquals(wantPath, realPath);
+
+ assertEquals(1, db.mConnection.size());
+ }
}
diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java
index 936f4d7..61e05da 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java
@@ -291,6 +291,17 @@
}
@Test
+ public void testCalculateBoundingRects_noBoundingRectsAndFrameNotAtOrigin_createsSingleRect() {
+ mSource.setFrame(new Rect(100, 100, 1200, 200));
+ mSource.setBoundingRects(null);
+
+ final Rect[] rects = mSource.calculateBoundingRects(new Rect(100, 100, 1100, 1100), false);
+
+ assertEquals(1, rects.length);
+ assertEquals(new Rect(0, 0, 1000, 100), rects[0]);
+ }
+
+ @Test
public void testCalculateBoundingRects_noBoundingRectsAndLargerFrame_singleRectFitsRelFrame() {
mSource.setFrame(new Rect(0, 0, 1000, 100));
mSource.setBoundingRects(null);
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
new file mode 100644
index 0000000..90a8c5c
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.os.SystemClock;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.coretests.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ViewFrameRateTest {
+
+ @Rule
+ public ActivityTestRule<ViewCaptureTestActivity> mActivityRule = new ActivityTestRule<>(
+ ViewCaptureTestActivity.class);
+
+ private Activity mActivity;
+ private View mMovingView;
+ private ViewRootImpl mViewRoot;
+
+ @Before
+ public void setUp() throws Throwable {
+ mActivity = mActivityRule.getActivity();
+ mActivityRule.runOnUiThread(() -> {
+ mActivity.setContentView(R.layout.view_velocity_test);
+ mMovingView = mActivity.findViewById(R.id.moving_view);
+ });
+ ViewParent parent = mActivity.getWindow().getDecorView().getParent();
+ while (parent instanceof View) {
+ parent = parent.getParent();
+ }
+ mViewRoot = (ViewRootImpl) parent;
+ }
+
+ @UiThreadTest
+ @Test
+ @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
+ public void frameRateChangesWhenContentMoves() {
+ mMovingView.offsetLeftAndRight(100);
+ float frameRate = mViewRoot.getPreferredFrameRate();
+ assertTrue(frameRate > 0);
+ }
+
+ @UiThreadTest
+ @Test
+ @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
+ public void firstFrameNoMovement() {
+ assertEquals(0f, mViewRoot.getPreferredFrameRate(), 0f);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
+ public void touchBoostDisable() throws Throwable {
+ mActivityRule.runOnUiThread(() -> {
+ long now = SystemClock.uptimeMillis();
+ MotionEvent down = MotionEvent.obtain(
+ /* downTime */ now,
+ /* eventTime */ now,
+ /* action */ MotionEvent.ACTION_DOWN,
+ /* x */ 0f,
+ /* y */ 0f,
+ /* metaState */ 0
+ );
+ mActivity.dispatchTouchEvent(down);
+ mMovingView.offsetLeftAndRight(10);
+ });
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.invalidate();
+ });
+
+ mActivityRule.runOnUiThread(() -> {
+ assertFalse(mViewRoot.getIsTouchBoosting());
+ });
+ }
+
+ private void waitForFrameRateCategoryToSettle() throws Throwable {
+ for (int i = 0; i < 5; i++) {
+ final CountDownLatch drawLatch = new CountDownLatch(1);
+
+ // Now that it is small, any invalidation should have a normal category
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.invalidate();
+ mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch::countDown);
+ });
+
+ assertTrue(drawLatch.await(1, TimeUnit.SECONDS));
+ }
+ }
+
+ @Test
+ public void noVelocityUsesCategorySmall() throws Throwable {
+ final CountDownLatch drawLatch1 = new CountDownLatch(1);
+ mActivityRule.runOnUiThread(() -> {
+ float density = mActivity.getResources().getDisplayMetrics().density;
+ ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
+ layoutParams.height = (int) (40 * density);
+ layoutParams.width = (int) (40 * density);
+ mMovingView.setLayoutParams(layoutParams);
+ mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
+ });
+
+ assertTrue(drawLatch1.await(1, TimeUnit.SECONDS));
+ waitForFrameRateCategoryToSettle();
+
+ // Now that it is small, any invalidation should have a normal category
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.invalidate();
+ assertEquals(Surface.FRAME_RATE_CATEGORY_NORMAL,
+ mViewRoot.getPreferredFrameRateCategory());
+ });
+ }
+
+ @Test
+ public void noVelocityUsesCategoryNarrowWidth() throws Throwable {
+ final CountDownLatch drawLatch1 = new CountDownLatch(1);
+ mActivityRule.runOnUiThread(() -> {
+ float density = mActivity.getResources().getDisplayMetrics().density;
+ ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
+ layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
+ layoutParams.width = (int) (10 * density);
+ mMovingView.setLayoutParams(layoutParams);
+ mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
+ });
+
+ assertTrue(drawLatch1.await(1, TimeUnit.SECONDS));
+ waitForFrameRateCategoryToSettle();
+
+ // Now that it is small, any invalidation should have a normal category
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.invalidate();
+ assertEquals(Surface.FRAME_RATE_CATEGORY_NORMAL,
+ mViewRoot.getPreferredFrameRateCategory());
+ });
+ }
+
+ @Test
+ public void noVelocityUsesCategoryNarrowHeight() throws Throwable {
+ final CountDownLatch drawLatch1 = new CountDownLatch(1);
+ mActivityRule.runOnUiThread(() -> {
+ float density = mActivity.getResources().getDisplayMetrics().density;
+ ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
+ layoutParams.height = (int) (10 * density);
+ layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ mMovingView.setLayoutParams(layoutParams);
+ mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
+ });
+
+ assertTrue(drawLatch1.await(1, TimeUnit.SECONDS));
+ waitForFrameRateCategoryToSettle();
+
+ // Now that it is small, any invalidation should have a normal category
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.invalidate();
+ assertEquals(Surface.FRAME_RATE_CATEGORY_NORMAL,
+ mViewRoot.getPreferredFrameRateCategory());
+ });
+ }
+
+ @Test
+ public void noVelocityUsesCategoryLargeWidth() throws Throwable {
+ final CountDownLatch drawLatch1 = new CountDownLatch(1);
+ mActivityRule.runOnUiThread(() -> {
+ float density = mActivity.getResources().getDisplayMetrics().density;
+ ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
+ layoutParams.height = (int) (40 * density);
+ layoutParams.width = (int) Math.ceil(41 * density);
+ mMovingView.setLayoutParams(layoutParams);
+ mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
+ });
+
+ assertTrue(drawLatch1.await(1, TimeUnit.SECONDS));
+ waitForFrameRateCategoryToSettle();
+
+ // Now that it is small, any invalidation should have a high category
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.invalidate();
+ assertEquals(Surface.FRAME_RATE_CATEGORY_HIGH,
+ mViewRoot.getPreferredFrameRateCategory());
+ });
+ }
+
+ @Test
+ public void noVelocityUsesCategoryLargeHeight() throws Throwable {
+ final CountDownLatch drawLatch1 = new CountDownLatch(1);
+ mActivityRule.runOnUiThread(() -> {
+ float density = mActivity.getResources().getDisplayMetrics().density;
+ ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
+ layoutParams.height = (int) Math.ceil(41 * density);
+ layoutParams.width = (int) (40 * density);
+ mMovingView.setLayoutParams(layoutParams);
+ mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
+ });
+
+ assertTrue(drawLatch1.await(1, TimeUnit.SECONDS));
+ waitForFrameRateCategoryToSettle();
+
+ // Now that it is small, any invalidation should have a high category
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.invalidate();
+ assertEquals(Surface.FRAME_RATE_CATEGORY_HIGH,
+ mViewRoot.getPreferredFrameRateCategory());
+ });
+ }
+}
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 1a242ef..2544fcb 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -19,6 +19,7 @@
import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR;
import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY;
+import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
@@ -796,6 +797,38 @@
}
/**
+ * When velocity of a View is not equal to 0, we call setFrameRateCategory with HIGH.
+ * Also, we shouldn't call setFrameRate.
+ */
+ @Test
+ @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY, FLAG_VIEW_VELOCITY_API})
+ public void votePreferredFrameRate_voteFrameRateCategory_velocityToHigh() {
+ View view = new View(sContext);
+ WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+ wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+ wmlp.width = 1;
+ wmlp.height = 1;
+
+ sInstrumentation.runOnMainSync(() -> {
+ WindowManager wm = sContext.getSystemService(WindowManager.class);
+ wm.addView(view, wmlp);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+
+ sInstrumentation.runOnMainSync(() -> {
+ assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1);
+ view.setFrameContentVelocity(100);
+ view.invalidate();
+ assertTrue(viewRootImpl.getPreferredFrameRate() > 0);
+ });
+ sInstrumentation.waitForIdleSync();
+ assertEquals(viewRootImpl.getLastPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ assertEquals(viewRootImpl.getLastPreferredFrameRate(), 0 , 0.1);
+ }
+
+ /**
* We should boost the frame rate if the value of mInsetsAnimationRunning is true.
*/
@Test
diff --git a/core/tests/coretests/src/android/view/ViewVelocityTest.java b/core/tests/coretests/src/android/view/ViewVelocityTest.java
deleted file mode 100644
index d437f7b..0000000
--- a/core/tests/coretests/src/android/view/ViewVelocityTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
-
-import static junit.framework.Assert.assertEquals;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.app.Activity;
-import android.os.SystemClock;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.filters.SmallTest;
-import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.frameworks.coretests.R;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class ViewVelocityTest {
-
- @Rule
- public ActivityTestRule<ViewCaptureTestActivity> mActivityRule = new ActivityTestRule<>(
- ViewCaptureTestActivity.class);
-
- private Activity mActivity;
- private View mMovingView;
- private ViewRootImpl mViewRoot;
-
- @Before
- public void setUp() throws Throwable {
- mActivity = mActivityRule.getActivity();
- mActivityRule.runOnUiThread(() -> {
- mActivity.setContentView(R.layout.view_velocity_test);
- mMovingView = mActivity.findViewById(R.id.moving_view);
- });
- ViewParent parent = mActivity.getWindow().getDecorView().getParent();
- while (parent instanceof View) {
- parent = parent.getParent();
- }
- mViewRoot = (ViewRootImpl) parent;
- }
-
- @UiThreadTest
- @Test
- @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
- public void frameRateChangesWhenContentMoves() {
- mMovingView.offsetLeftAndRight(100);
- float frameRate = mViewRoot.getPreferredFrameRate();
- assertTrue(frameRate > 0);
- }
-
- @UiThreadTest
- @Test
- @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
- public void firstFrameNoMovement() {
- assertEquals(0f, mViewRoot.getPreferredFrameRate(), 0f);
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
- public void touchBoostDisable() throws Throwable {
- mActivityRule.runOnUiThread(() -> {
- long now = SystemClock.uptimeMillis();
- MotionEvent down = MotionEvent.obtain(
- /* downTime */ now,
- /* eventTime */ now,
- /* action */ MotionEvent.ACTION_DOWN,
- /* x */ 0f,
- /* y */ 0f,
- /* metaState */ 0
- );
- mActivity.dispatchTouchEvent(down);
- mMovingView.offsetLeftAndRight(10);
- });
- mActivityRule.runOnUiThread(() -> {
- mMovingView.invalidate();
- });
-
- mActivityRule.runOnUiThread(() -> {
- assertFalse(mViewRoot.getIsTouchBoosting());
- });
- }
-}
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
index e2f2554..1013bf5 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
@@ -16,13 +16,24 @@
package android.view.accessibility;
+import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME;
+
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.TestCase.assertFalse;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -34,6 +45,8 @@
import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.RemoteException;
import android.os.UserHandle;
import androidx.annotation.NonNull;
@@ -41,6 +54,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
+import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
import com.android.internal.util.IntPair;
import com.android.server.accessibility.test.MessageCapturingHandler;
@@ -54,6 +68,8 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.concurrent.Executors;
/**
@@ -263,6 +279,75 @@
verify(mMockService).unregisterProxyForDisplay(proxy.getDisplayId());
}
+ @Test
+ public void getA11yFeatureToTileMap_catchRemoteExceptionAndRethrow() throws Exception {
+ AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
+ doThrow(new RemoteException(new SecurityException()))
+ .when(mMockService)
+ .getA11yFeatureToTileMap(anyInt());
+
+ Throwable rethrownException = assertThrows(RuntimeException.class,
+ () -> manager.getA11yFeatureToTileMap(UserHandle.USER_CURRENT));
+ assertThat(rethrownException.getCause().getCause()).isInstanceOf(SecurityException.class);
+ }
+
+ @Test
+ public void getA11yFeatureToTileMap_verifyServiceMethodCalled() throws Exception {
+ AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(
+ COLOR_INVERSION_COMPONENT_NAME.flattenToString(),
+ COLOR_INVERSION_TILE_COMPONENT_NAME);
+ bundle.putParcelable(
+ DALTONIZER_COMPONENT_NAME.flattenToString(),
+ DALTONIZER_TILE_COMPONENT_NAME);
+ when(mMockService.getA11yFeatureToTileMap(UserHandle.USER_CURRENT)).thenReturn(bundle);
+
+ assertThat(manager.getA11yFeatureToTileMap(UserHandle.USER_CURRENT))
+ .containsExactlyEntriesIn(Map.of(
+ COLOR_INVERSION_COMPONENT_NAME, COLOR_INVERSION_TILE_COMPONENT_NAME,
+ DALTONIZER_COMPONENT_NAME, DALTONIZER_TILE_COMPONENT_NAME
+ ));
+ verify(mMockService).getA11yFeatureToTileMap(UserHandle.USER_CURRENT);
+ }
+
+ @Test
+ public void enableShortcutsForTargets_catchRemoteExceptionAndRethrow() throws Exception {
+ AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
+ doThrow(new RemoteException(new SecurityException()))
+ .when(mMockService)
+ .enableShortcutsForTargets(anyBoolean(), anyInt(), anyList(), anyInt());
+
+ Throwable rethrownException = assertThrows(RuntimeException.class,
+ () -> manager.enableShortcutsForTargets(
+ /* enable= */ false,
+ UserShortcutType.HARDWARE,
+ Set.of(DALTONIZER_COMPONENT_NAME.flattenToString()),
+ UserHandle.USER_CURRENT
+ ));
+ assertThat(rethrownException.getCause().getCause()).isInstanceOf(SecurityException.class);
+ }
+
+ @Test
+ public void enableShortcutsForTargets_verifyServiceMethodCalled() throws Exception {
+ AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
+ int shortcutTypes = UserShortcutType.HARDWARE | UserShortcutType.TRIPLETAP;
+
+ manager.enableShortcutsForTargets(
+ /* enable= */ false,
+ shortcutTypes,
+ Set.of(DALTONIZER_COMPONENT_NAME.flattenToString()),
+ UserHandle.USER_CURRENT
+ );
+
+ verify(mMockService).enableShortcutsForTargets(
+ /* enable= */ false,
+ shortcutTypes,
+ List.of(DALTONIZER_COMPONENT_NAME.flattenToString()),
+ UserHandle.USER_CURRENT
+ );
+ }
+
private class MyAccessibilityProxy extends AccessibilityDisplayProxy {
MyAccessibilityProxy(int displayId,
@NonNull List<AccessibilityServiceInfo> serviceInfos) {
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index b60b806f..a5c9624 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -23,6 +23,8 @@
import static android.view.inputmethod.Flags.initiationWithoutInputConnection;
import static android.view.stylus.HandwritingTestUtil.createView;
+import static com.android.text.flags.Flags.handwritingCursorPosition;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeFalse;
@@ -129,6 +131,7 @@
public void onTouchEvent_startHandwriting_when_stylusMoveOnce_withinHWArea() {
mTestView1.setText("hello");
when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
+ when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(0);
mHandwritingInitiator.onInputConnectionCreated(mTestView1);
final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
@@ -148,9 +151,51 @@
// After IMM.startHandwriting is triggered, onTouchEvent should return true for ACTION_MOVE
// events so that the events are not dispatched to the view tree.
assertThat(onTouchEventResult2).isTrue();
- // Since the stylus down point was inside the TextView's bounds, the handwriting initiator
- // does not need to set the cursor position.
- verify(mTestView1, never()).setSelection(anyInt());
+ if (handwritingCursorPosition()) {
+ // Cursor is placed at the end of the text.
+ verify(mTestView1).setSelection(5);
+ } else {
+ // Since the stylus down point was inside the TextView's bounds, the handwriting
+ // initiator does not need to set the cursor position.
+ verify(mTestView1, never()).setSelection(anyInt());
+ }
+ }
+
+ @Test
+ public void onTouchEvent_startHandwriting_multipleParagraphs() {
+ // End of line 0 is offset 10, end of line 1 is offset 20, end of line 2 is offset 30, end
+ // of line 3 is offset 40.
+ mTestView1.setText("line 0 \nline 1 \nline 2 \nline 3 ");
+ mTestView1.layout(0, 0, 500, 500);
+ when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
+ when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(2);
+
+ mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+ final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
+ final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2;
+ MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
+ boolean onTouchEventResult1 = mHandwritingInitiator.onTouchEvent(stylusEvent1);
+
+ final int x2 = x1 + mHandwritingSlop * 2;
+ final int y2 = y1;
+
+ MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
+ boolean onTouchEventResult2 = mHandwritingInitiator.onTouchEvent(stylusEvent2);
+
+ // Stylus movement within HandwritingArea should trigger IMM.startHandwriting once.
+ verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
+ assertThat(onTouchEventResult1).isFalse();
+ // After IMM.startHandwriting is triggered, onTouchEvent should return true for ACTION_MOVE
+ // events so that the events are not dispatched to the view tree.
+ assertThat(onTouchEventResult2).isTrue();
+ if (handwritingCursorPosition()) {
+ // Cursor is placed at the end of the paragraph containing line 2.
+ verify(mTestView1).setSelection(30);
+ } else {
+ // Since the stylus down point was inside the TextView's bounds, the handwriting
+ // initiator does not need to set the cursor position.
+ verify(mTestView1, never()).setSelection(anyInt());
+ }
}
@Test
@@ -197,6 +242,7 @@
public void onTouchEvent_startHandwriting_when_stylusMove_withinExtendedHWArea() {
mTestView1.setText("hello");
when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
+ when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(0);
if (!mInitiateWithoutConnection) {
mHandwritingInitiator.onInputConnectionCreated(mTestView1);
@@ -214,9 +260,14 @@
// Stylus movement within extended HandwritingArea should trigger IMM.startHandwriting once.
verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
- // Since the stylus down point was outside the TextView's bounds, the handwriting initiator
- // sets the cursor position.
- verify(mTestView1).setSelection(4);
+ if (handwritingCursorPosition()) {
+ // Cursor is placed at the end of the text.
+ verify(mTestView1).setSelection(5);
+ } else {
+ // Since the stylus down point was outside the TextView's bounds, the handwriting
+ // initiator sets the cursor position.
+ verify(mTestView1).setSelection(4);
+ }
}
@Test
@@ -246,6 +297,8 @@
public void onTouchEvent_startHandwriting_servedViewUpdate_stylusMoveInExtendedHWArea() {
mTestView1.setText("hello");
when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
+ when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(0);
+
// The stylus down point is between mTestView1 and mTestView2, but it is within the
// extended handwriting area of both views. It is closer to mTestView1.
final int x1 = sHwArea1.right + HW_BOUNDS_OFFSETS_RIGHT_PX / 2;
@@ -278,9 +331,14 @@
// Handwriting is started for this view since the stylus down point is closest to this
// view.
verify(mHandwritingInitiator).startHandwriting(mTestView1);
- // Since the stylus down point was outside the TextView's bounds, the handwriting initiator
- // sets the cursor position.
- verify(mTestView1).setSelection(4);
+ if (handwritingCursorPosition()) {
+ // Cursor is placed at the end of the text.
+ verify(mTestView1).setSelection(5);
+ } else {
+ // Since the stylus down point was outside the TextView's bounds, the handwriting
+ // initiator sets the cursor position.
+ verify(mTestView1).setSelection(4);
+ }
}
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java
index dcef0a7..0897726 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java
@@ -16,20 +16,31 @@
package android.hardware.devicestate;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import android.os.Parcel;
import androidx.test.filters.SmallTest;
+import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.util.List;
+import java.util.Set;
+
/**
* Unit tests for {@link DeviceStateInfo}.
* <p/>
@@ -38,11 +49,34 @@
@RunWith(JUnit4.class)
@SmallTest
public final class DeviceStateInfoTest {
+
+ private static final DeviceState DEVICE_STATE_0 = new DeviceState(
+ new DeviceState.Configuration.Builder(0, "STATE_0")
+ .setSystemProperties(
+ Set.of(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS,
+ PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY))
+ .setPhysicalProperties(
+ Set.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED))
+ .build());
+ private static final DeviceState DEVICE_STATE_1 = new DeviceState(
+ new DeviceState.Configuration.Builder(1, "STATE_1")
+ .setSystemProperties(
+ Set.of(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY))
+ .setPhysicalProperties(
+ Set.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN))
+ .build());
+ private static final DeviceState DEVICE_STATE_2 = new DeviceState(
+ new DeviceState.Configuration.Builder(2, "STATE_2")
+ .setSystemProperties(
+ Set.of(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY))
+ .build());
+
@Test
public void create() {
- final int[] supportedStates = new int[] { 0, 1, 2 };
- final int baseState = 0;
- final int currentState = 2;
+ final List<DeviceState> supportedStates = List.of(DEVICE_STATE_0, DEVICE_STATE_1,
+ DEVICE_STATE_2);
+ final DeviceState baseState = DEVICE_STATE_0;
+ final DeviceState currentState = DEVICE_STATE_2;
final DeviceStateInfo info = new DeviceStateInfo(supportedStates, baseState, currentState);
assertNotNull(info.supportedStates);
@@ -53,27 +87,30 @@
@Test
public void equals() {
- final int[] supportedStates = new int[] { 0, 1, 2 };
- final int baseState = 0;
- final int currentState = 2;
+ final List<DeviceState> supportedStates = List.of(DEVICE_STATE_0, DEVICE_STATE_1,
+ DEVICE_STATE_2);
+ final DeviceState baseState = DEVICE_STATE_0;
+ final DeviceState currentState = DEVICE_STATE_2;
final DeviceStateInfo info = new DeviceStateInfo(supportedStates, baseState, currentState);
- assertTrue(info.equals(info));
+ Assert.assertEquals(info, info);
final DeviceStateInfo sameInfo = new DeviceStateInfo(supportedStates, baseState,
currentState);
- assertTrue(info.equals(sameInfo));
+ Assert.assertEquals(info, sameInfo);
- final DeviceStateInfo differentInfo = new DeviceStateInfo(new int[]{ 0, 2}, baseState,
+ final DeviceStateInfo differentInfo = new DeviceStateInfo(
+ List.of(DEVICE_STATE_0, DEVICE_STATE_2), baseState,
currentState);
- assertFalse(info.equals(differentInfo));
+ assertNotEquals(info, differentInfo);
}
@Test
public void diff_sameObject() {
- final int[] supportedStates = new int[] { 0, 1, 2 };
- final int baseState = 0;
- final int currentState = 2;
+ final List<DeviceState> supportedStates = List.of(DEVICE_STATE_0, DEVICE_STATE_1,
+ DEVICE_STATE_2);
+ final DeviceState baseState = DEVICE_STATE_0;
+ final DeviceState currentState = DEVICE_STATE_2;
final DeviceStateInfo info = new DeviceStateInfo(supportedStates, baseState, currentState);
assertEquals(0, info.diff(info));
@@ -81,8 +118,10 @@
@Test
public void diff_differentSupportedStates() {
- final DeviceStateInfo info = new DeviceStateInfo(new int[] { 1 }, 0, 0);
- final DeviceStateInfo otherInfo = new DeviceStateInfo(new int[] { 2 }, 0, 0);
+ final DeviceStateInfo info = new DeviceStateInfo(List.of(DEVICE_STATE_1), DEVICE_STATE_0,
+ DEVICE_STATE_0);
+ final DeviceStateInfo otherInfo = new DeviceStateInfo(List.of(DEVICE_STATE_2),
+ DEVICE_STATE_0, DEVICE_STATE_0);
final int diff = info.diff(otherInfo);
assertTrue((diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES) > 0);
assertFalse((diff & DeviceStateInfo.CHANGED_BASE_STATE) > 0);
@@ -91,8 +130,10 @@
@Test
public void diff_differentNonOverrideState() {
- final DeviceStateInfo info = new DeviceStateInfo(new int[] { 1 }, 1, 0);
- final DeviceStateInfo otherInfo = new DeviceStateInfo(new int[] { 1 }, 2, 0);
+ final DeviceStateInfo info = new DeviceStateInfo(List.of(DEVICE_STATE_1), DEVICE_STATE_1,
+ DEVICE_STATE_0);
+ final DeviceStateInfo otherInfo = new DeviceStateInfo(List.of(DEVICE_STATE_1),
+ DEVICE_STATE_2, DEVICE_STATE_0);
final int diff = info.diff(otherInfo);
assertFalse((diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES) > 0);
assertTrue((diff & DeviceStateInfo.CHANGED_BASE_STATE) > 0);
@@ -101,8 +142,10 @@
@Test
public void diff_differentState() {
- final DeviceStateInfo info = new DeviceStateInfo(new int[] { 1 }, 0, 1);
- final DeviceStateInfo otherInfo = new DeviceStateInfo(new int[] { 1 }, 0, 2);
+ final DeviceStateInfo info = new DeviceStateInfo(List.of(DEVICE_STATE_1), DEVICE_STATE_0,
+ DEVICE_STATE_1);
+ final DeviceStateInfo otherInfo = new DeviceStateInfo(List.of(DEVICE_STATE_1),
+ DEVICE_STATE_0, DEVICE_STATE_2);
final int diff = info.diff(otherInfo);
assertFalse((diff & DeviceStateInfo.CHANGED_SUPPORTED_STATES) > 0);
assertFalse((diff & DeviceStateInfo.CHANGED_BASE_STATE) > 0);
@@ -111,9 +154,10 @@
@Test
public void writeToParcel() {
- final int[] supportedStates = new int[] { 0, 1, 2 };
- final int nonOverrideState = 0;
- final int state = 2;
+ final List<DeviceState> supportedStates = List.of(DEVICE_STATE_0, DEVICE_STATE_1,
+ DEVICE_STATE_2);
+ final DeviceState nonOverrideState = DEVICE_STATE_0;
+ final DeviceState state = DEVICE_STATE_2;
final DeviceStateInfo originalInfo =
new DeviceStateInfo(supportedStates, nonOverrideState, state);
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
index 03c38cc..ee238c0 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
@@ -41,6 +41,7 @@
import org.junit.runners.JUnit4;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
/**
@@ -51,8 +52,10 @@
@RunWith(JUnit4.class)
@SmallTest
public final class DeviceStateManagerGlobalTest {
- private static final int DEFAULT_DEVICE_STATE = 0;
- private static final int OTHER_DEVICE_STATE = 1;
+ private static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(
+ new DeviceState.Configuration.Builder(0 /* identifier */, "" /* name */).build());
+ private static final DeviceState OTHER_DEVICE_STATE = new DeviceState(
+ new DeviceState.Configuration.Builder(1 /* identifier */, "" /* name */).build());
private TestDeviceStateManagerService mService;
private DeviceStateManagerGlobal mDeviceStateManagerGlobal;
@@ -76,41 +79,37 @@
ConcurrentUtils.DIRECT_EXECUTOR);
// Verify initial callbacks
- verify(callback1).onSupportedStatesChanged(eq(mService.getSupportedStates()));
- verify(callback1).onBaseStateChanged(eq(mService.getBaseState()));
- verify(callback1).onStateChanged(eq(mService.getMergedState()));
- verify(callback2).onSupportedStatesChanged(eq(mService.getSupportedStates()));
- verify(callback2).onBaseStateChanged(eq(mService.getBaseState()));
- verify(callback2).onStateChanged(eq(mService.getMergedState()));
+ verify(callback1).onSupportedStatesChanged(eq(mService.getSupportedDeviceStates()));
+ verify(callback1).onDeviceStateChanged(eq(mService.getMergedState()));
+ verify(callback2).onDeviceStateChanged(eq(mService.getMergedState()));
reset(callback1);
reset(callback2);
// Change the supported states and verify callback
- mService.setSupportedStates(new int[]{ DEFAULT_DEVICE_STATE });
- verify(callback1).onSupportedStatesChanged(eq(mService.getSupportedStates()));
- verify(callback2).onSupportedStatesChanged(eq(mService.getSupportedStates()));
- mService.setSupportedStates(new int[]{ DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE });
+ mService.setSupportedStates(List.of(DEFAULT_DEVICE_STATE));
+ verify(callback1).onSupportedStatesChanged(eq(mService.getSupportedDeviceStates()));
+ verify(callback2).onSupportedStatesChanged(eq(mService.getSupportedDeviceStates()));
+ mService.setSupportedStates(List.of(DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE));
reset(callback1);
reset(callback2);
// Change the base state and verify callback
mService.setBaseState(OTHER_DEVICE_STATE);
- verify(callback1).onBaseStateChanged(eq(mService.getBaseState()));
- verify(callback1).onStateChanged(eq(mService.getMergedState()));
- verify(callback2).onBaseStateChanged(eq(mService.getBaseState()));
- verify(callback2).onStateChanged(eq(mService.getMergedState()));
+ verify(callback1).onDeviceStateChanged(eq(mService.getMergedState()));
+ verify(callback2).onDeviceStateChanged(eq(mService.getMergedState()));
reset(callback1);
reset(callback2);
// Change the requested state and verify callback
- DeviceStateRequest request = DeviceStateRequest.newBuilder(DEFAULT_DEVICE_STATE).build();
+ DeviceStateRequest request = DeviceStateRequest.newBuilder(
+ DEFAULT_DEVICE_STATE.getIdentifier()).build();
mDeviceStateManagerGlobal.requestState(request, null /* executor */, null /* callback */);
- verify(callback1).onStateChanged(eq(mService.getMergedState()));
- verify(callback2).onStateChanged(eq(mService.getMergedState()));
+ verify(callback1).onDeviceStateChanged(eq(mService.getMergedState()));
+ verify(callback2).onDeviceStateChanged(eq(mService.getMergedState()));
}
@Test
@@ -121,14 +120,13 @@
ConcurrentUtils.DIRECT_EXECUTOR);
// Verify initial callbacks
- verify(callback).onSupportedStatesChanged(eq(mService.getSupportedStates()));
- verify(callback).onBaseStateChanged(eq(mService.getBaseState()));
- verify(callback).onStateChanged(eq(mService.getMergedState()));
+ verify(callback).onSupportedStatesChanged(eq(mService.getSupportedDeviceStates()));
+ verify(callback).onDeviceStateChanged(eq(mService.getMergedState()));
reset(callback);
mDeviceStateManagerGlobal.unregisterDeviceStateCallback(callback);
- mService.setSupportedStates(new int[]{OTHER_DEVICE_STATE});
+ mService.setSupportedStates(List.of(OTHER_DEVICE_STATE));
mService.setBaseState(OTHER_DEVICE_STATE);
verifyZeroInteractions(callback);
}
@@ -139,18 +137,19 @@
mDeviceStateManagerGlobal.registerDeviceStateCallback(callback,
ConcurrentUtils.DIRECT_EXECUTOR);
- verify(callback).onStateChanged(eq(mService.getBaseState()));
+ verify(callback).onDeviceStateChanged(eq(mService.getBaseState()));
reset(callback);
- DeviceStateRequest request = DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE).build();
+ DeviceStateRequest request = DeviceStateRequest.newBuilder(
+ OTHER_DEVICE_STATE.getIdentifier()).build();
mDeviceStateManagerGlobal.requestState(request, null /* executor */, null /* callback */);
- verify(callback).onStateChanged(eq(OTHER_DEVICE_STATE));
+ verify(callback).onDeviceStateChanged(eq(OTHER_DEVICE_STATE));
reset(callback);
mDeviceStateManagerGlobal.cancelStateRequest();
- verify(callback).onStateChanged(eq(mService.getBaseState()));
+ verify(callback).onDeviceStateChanged(eq(mService.getBaseState()));
}
@Test
@@ -159,22 +158,20 @@
mDeviceStateManagerGlobal.registerDeviceStateCallback(callback,
ConcurrentUtils.DIRECT_EXECUTOR);
- verify(callback).onBaseStateChanged(eq(mService.getBaseState()));
- verify(callback).onStateChanged(eq(mService.getBaseState()));
+ verify(callback).onDeviceStateChanged(eq(mService.getBaseState()));
reset(callback);
- DeviceStateRequest request = DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE).build();
+ DeviceStateRequest request = DeviceStateRequest.newBuilder(
+ OTHER_DEVICE_STATE.getIdentifier()).build();
mDeviceStateManagerGlobal.requestBaseStateOverride(request, null /* executor */,
null /* callback */);
- verify(callback).onBaseStateChanged(eq(OTHER_DEVICE_STATE));
- verify(callback).onStateChanged(eq(OTHER_DEVICE_STATE));
+ verify(callback).onDeviceStateChanged(eq(OTHER_DEVICE_STATE));
reset(callback);
mDeviceStateManagerGlobal.cancelBaseStateOverride();
- verify(callback).onBaseStateChanged(eq(mService.getBaseState()));
- verify(callback).onStateChanged(eq(mService.getBaseState()));
+ verify(callback).onDeviceStateChanged(eq(mService.getBaseState()));
}
@Test
@@ -183,44 +180,43 @@
mDeviceStateManagerGlobal.registerDeviceStateCallback(callback,
ConcurrentUtils.DIRECT_EXECUTOR);
- verify(callback).onBaseStateChanged(eq(mService.getBaseState()));
- verify(callback).onStateChanged(eq(mService.getBaseState()));
+ verify(callback).onDeviceStateChanged(eq(mService.getBaseState()));
reset(callback);
- DeviceStateRequest request = DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE).build();
+ DeviceStateRequest request = DeviceStateRequest.newBuilder(
+ OTHER_DEVICE_STATE.getIdentifier()).build();
mDeviceStateManagerGlobal.requestBaseStateOverride(request, null /* executor */,
null /* callback */);
- verify(callback).onBaseStateChanged(eq(OTHER_DEVICE_STATE));
- verify(callback).onStateChanged(eq(OTHER_DEVICE_STATE));
+ verify(callback).onDeviceStateChanged(eq(OTHER_DEVICE_STATE));
assertEquals(OTHER_DEVICE_STATE, mService.getBaseState());
reset(callback);
DeviceStateRequest secondRequest = DeviceStateRequest.newBuilder(
- DEFAULT_DEVICE_STATE).build();
+ DEFAULT_DEVICE_STATE.getIdentifier()).build();
mDeviceStateManagerGlobal.requestState(secondRequest, null, null);
assertEquals(OTHER_DEVICE_STATE, mService.getBaseState());
- verify(callback).onStateChanged(eq(DEFAULT_DEVICE_STATE));
+ verify(callback).onDeviceStateChanged(eq(DEFAULT_DEVICE_STATE));
reset(callback);
mDeviceStateManagerGlobal.cancelStateRequest();
- verify(callback).onStateChanged(OTHER_DEVICE_STATE);
+ verify(callback).onDeviceStateChanged(OTHER_DEVICE_STATE);
reset(callback);
mDeviceStateManagerGlobal.cancelBaseStateOverride();
- verify(callback).onBaseStateChanged(DEFAULT_DEVICE_STATE);
- verify(callback).onStateChanged(DEFAULT_DEVICE_STATE);
+ verify(callback).onDeviceStateChanged(DEFAULT_DEVICE_STATE);
}
@Test
public void verifyDeviceStateRequestCallbacksCalled() {
DeviceStateRequest.Callback callback = mock(TestDeviceStateRequestCallback.class);
- DeviceStateRequest request = DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE).build();
+ DeviceStateRequest request = DeviceStateRequest.newBuilder(
+ OTHER_DEVICE_STATE.getIdentifier()).build();
mDeviceStateManagerGlobal.requestState(request,
ConcurrentUtils.DIRECT_EXECUTOR /* executor */,
callback /* callback */);
@@ -257,12 +253,14 @@
}
}
- private int[] mSupportedStates = new int[] { DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE };
- private int mBaseState = DEFAULT_DEVICE_STATE;
+ private List<DeviceState> mSupportedDeviceStates = List.of(DEFAULT_DEVICE_STATE,
+ OTHER_DEVICE_STATE);
+
+ private DeviceState mBaseState = DEFAULT_DEVICE_STATE;
private Request mRequest;
private Request mBaseStateRequest;
- private Set<IDeviceStateManagerCallback> mCallbacks = new HashSet<>();
+ private final Set<IDeviceStateManagerCallback> mCallbacks = new HashSet<>();
TestDeviceStateManagerService(FakePermissionEnforcer enforcer) {
super(enforcer);
@@ -270,10 +268,15 @@
private DeviceStateInfo getInfo() {
final int mergedBaseState = mBaseStateRequest == null
- ? mBaseState : mBaseStateRequest.state;
+ ? mBaseState.getIdentifier() : mBaseStateRequest.state;
final int mergedState = mRequest == null
? mergedBaseState : mRequest.state;
- return new DeviceStateInfo(mSupportedStates, mergedBaseState, mergedState);
+
+ final DeviceState baseState = new DeviceState(
+ new DeviceState.Configuration.Builder(mergedBaseState, "" /* name */).build());
+ final DeviceState state = new DeviceState(
+ new DeviceState.Configuration.Builder(mergedState, "" /* name */).build());
+ return new DeviceStateInfo(mSupportedDeviceStates, baseState, state);
}
private void notifyDeviceStateInfoChanged() {
@@ -392,25 +395,25 @@
onStateRequestOverlayDismissed_enforcePermission();
}
- public void setSupportedStates(int[] states) {
- mSupportedStates = states;
+ public void setSupportedStates(List<DeviceState> states) {
+ mSupportedDeviceStates = states;
notifyDeviceStateInfoChanged();
}
- public int[] getSupportedStates() {
- return mSupportedStates;
+ public List<DeviceState> getSupportedDeviceStates() {
+ return mSupportedDeviceStates;
}
- public void setBaseState(int state) {
+ public void setBaseState(DeviceState state) {
mBaseState = state;
notifyDeviceStateInfoChanged();
}
- public int getBaseState() {
+ public DeviceState getBaseState() {
return getInfo().baseState;
}
- public int getMergedState() {
+ public DeviceState getMergedState() {
return getInfo().currentState;
}
}
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
index c287721..78d4324 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
@@ -16,19 +16,27 @@
package android.hardware.devicestate;
-import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE_IDENTIFIER;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS;
import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE_IDENTIFIER;
import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNull;
-import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.assertTrue;
+import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
+import junit.framework.Assert;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
/**
* Unit tests for {@link android.hardware.devicestate.DeviceState}.
* <p/>
@@ -39,33 +47,68 @@
public final class DeviceStateTest {
@Test
public void testConstruct() {
- final DeviceState state = new DeviceState(MINIMUM_DEVICE_STATE_IDENTIFIER /* identifier */,
- "TEST_CLOSED" /* name */, DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS /* flags */);
+ DeviceState.Configuration config = new DeviceState.Configuration.Builder(
+ MINIMUM_DEVICE_STATE_IDENTIFIER, "TEST_CLOSED")
+ .setSystemProperties(
+ new HashSet<>(List.of(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS)))
+ .build();
+ final DeviceState state = new DeviceState(config);
assertEquals(state.getIdentifier(), MINIMUM_DEVICE_STATE_IDENTIFIER);
assertEquals(state.getName(), "TEST_CLOSED");
- assertEquals(state.getFlags(), DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS);
+ assertTrue(state.hasProperty(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS));
}
@Test
- public void testConstruct_nullName() {
- final DeviceState state = new DeviceState(MAXIMUM_DEVICE_STATE_IDENTIFIER /* identifier */,
- null /* name */, 0/* flags */);
- assertEquals(state.getIdentifier(), MAXIMUM_DEVICE_STATE_IDENTIFIER);
- assertNull(state.getName());
- assertEquals(state.getFlags(), 0);
+ public void testHasProperties() {
+ DeviceState.Configuration config = new DeviceState.Configuration.Builder(
+ MINIMUM_DEVICE_STATE_IDENTIFIER, "TEST")
+ .setSystemProperties(new HashSet<>(List.of(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS,
+ PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST)))
+ .build();
+
+ final DeviceState state = new DeviceState(config);
+
+ assertTrue(state.hasProperty(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS));
+ assertTrue(state.hasProperty(PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST));
+ assertTrue(state.hasProperties(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS,
+ PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST));
}
@Test
- public void testConstruct_tooLargeIdentifier() {
- assertThrows(IllegalArgumentException.class,
- () -> new DeviceState(MAXIMUM_DEVICE_STATE_IDENTIFIER + 1 /* identifier */,
- null /* name */, 0 /* flags */));
+ public void writeToParcel() {
+ final DeviceState originalState = new DeviceState(
+ new DeviceState.Configuration.Builder(0, "TEST_STATE")
+ .setSystemProperties(Set.of(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS,
+ PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST))
+ .setPhysicalProperties(
+ Set.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED))
+ .build());
+
+ final Parcel parcel = Parcel.obtain();
+ originalState.getConfiguration().writeToParcel(parcel, 0 /* flags */);
+ parcel.setDataPosition(0);
+
+ final DeviceState.Configuration stateConfiguration =
+ DeviceState.Configuration.CREATOR.createFromParcel(parcel);
+
+ Assert.assertEquals(originalState, new DeviceState(stateConfiguration));
}
@Test
- public void testConstruct_tooSmallIdentifier() {
- assertThrows(IllegalArgumentException.class,
- () -> new DeviceState(MINIMUM_DEVICE_STATE_IDENTIFIER - 1 /* identifier */,
- null /* name */, 0 /* flags */));
+ public void writeToParcel_noPhysicalProperties() {
+ final DeviceState originalState = new DeviceState(
+ new DeviceState.Configuration.Builder(0, "TEST_STATE")
+ .setSystemProperties(Set.of(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS,
+ PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST))
+ .build());
+
+ final Parcel parcel = Parcel.obtain();
+ originalState.getConfiguration().writeToParcel(parcel, 0 /* flags */);
+ parcel.setDataPosition(0);
+
+ final DeviceState.Configuration stateConfiguration =
+ DeviceState.Configuration.CREATOR.createFromParcel(parcel);
+
+ Assert.assertEquals(originalState, new DeviceState(stateConfiguration));
}
}
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
index 245f216..701d145 100644
--- a/data/etc/OWNERS
+++ b/data/etc/OWNERS
@@ -12,3 +12,4 @@
per-file preinstalled-packages* = file:/MULTIUSER_OWNERS
per-file services.core.protolog.json = file:/services/core/java/com/android/server/wm/OWNERS
+per-file core.protolog.pb = file:/services/core/java/com/android/server/wm/OWNERS
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index ce2543a..bca741f 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -88,5 +88,8 @@
<permission name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS" />
<permission name="android.permission.READ_SEARCH_INDEXABLES" />
<permission name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"/>
+ <permission name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS" />
+ <permission name="android.permission.CONTROL_UI_TRACING" />
+ <permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND" />
</privapp-permissions>
</permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 0f12438..749f0e1 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -553,6 +553,8 @@
<permission name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"/>
<!-- Permission required for CTS test - CtsWearableSensingServiceTestCases -->
<permission name="android.permission.MANAGE_WEARABLE_SENSING_SERVICE"/>
+ <!-- Permission required for CTS test - OnDeviceIntelligenceManagerTest -->
+ <permission name="android.permission.USE_ON_DEVICE_INTELLIGENCE" />
<!-- Permission required for CTS test - CtsTelephonyProviderTestCases -->
<permission name="android.permission.WRITE_APN_SETTINGS"/>
<!-- Permission required for GTS test - GtsStatsdHostTestCases -->
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index a7acaf9..a2a0f49 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -333,6 +333,11 @@
nDrawPath(mNativeCanvasWrapper, path.readOnlyNI(), paint.getNativeInstance());
}
+ public void drawRegion(@NonNull Region region, @NonNull Paint paint) {
+ throwIfHasHwFeaturesInSwMode(paint);
+ nDrawRegion(mNativeCanvasWrapper, region.mNativeRegion, paint.getNativeInstance());
+ }
+
public void drawPoint(float x, float y, @NonNull Paint paint) {
throwIfHasHwFeaturesInSwMode(paint);
nDrawPoint(mNativeCanvasWrapper, x, y, paint.getNativeInstance());
diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java
index 4e88b0e..5b1fa7b 100644
--- a/graphics/java/android/graphics/BaseRecordingCanvas.java
+++ b/graphics/java/android/graphics/BaseRecordingCanvas.java
@@ -290,6 +290,11 @@
}
@Override
+ public void drawRegion(@NonNull Region region, @NonNull Paint paint) {
+ nDrawRegion(mNativeCanvasWrapper, region.mNativeRegion, paint.getNativeInstance());
+ }
+
+ @Override
public final void drawPicture(@NonNull Picture picture) {
picture.endRecording();
int restoreCount = save();
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index f9ac02a..e03a1da 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -1931,6 +1931,17 @@
}
/**
+ * Draws the given region using the given paint.
+ *
+ * @param region The region to be drawn
+ * @param paint The paint used to draw the region
+ */
+ @FlaggedApi(Flags.FLAG_DRAW_REGION)
+ public void drawRegion(@NonNull Region region, @NonNull Paint paint) {
+ super.drawRegion(region, paint);
+ }
+
+ /**
* Helper for drawPoints() for drawing a single point.
*/
public void drawPoint(float x, float y, @NonNull Paint paint) {
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index 20e393e..940cd93 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -25,6 +25,8 @@
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
+import android.hardware.DataSpace;
+import android.hardware.HardwareBuffer;
import android.hardware.OverlayProperties;
import android.hardware.display.DisplayManager;
import android.os.IBinder;
@@ -1417,7 +1419,14 @@
nInitDisplayInfo(largestWidth, largestHeight, defaultDisplay.getRefreshRate(),
wideColorDataspace, defaultDisplay.getAppVsyncOffsetNanos(),
defaultDisplay.getPresentationDeadlineNanos(),
- overlayProperties.isFp16SupportedForHdr(),
+ overlayProperties.isCombinationSupported(
+ DataSpace.DATASPACE_SCRGB, HardwareBuffer.RGBA_FP16),
+ overlayProperties.isCombinationSupported(
+ DataSpace.pack(
+ DataSpace.STANDARD_DCI_P3,
+ DataSpace.TRANSFER_SRGB,
+ DataSpace.RANGE_EXTENDED),
+ HardwareBuffer.RGBA_10101010),
overlayProperties.isMixedColorSpacesSupported());
mDisplayInitialized = true;
@@ -1603,7 +1612,8 @@
private static native void nInitDisplayInfo(int width, int height, float refreshRate,
int wideColorDataspace, long appVsyncOffsetNanos, long presentationDeadlineNanos,
- boolean supportsFp16ForHdr, boolean nInitDisplayInfo);
+ boolean supportsFp16ForHdr, boolean isRgba10101010SupportedForHdr,
+ boolean nSupportMixedColorSpaces);
private static native void nSetDrawingEnabled(boolean drawingEnabled);
diff --git a/graphics/java/android/graphics/pdf/PdfEditor.java b/graphics/java/android/graphics/pdf/PdfEditor.java
index 3cd709e..69e1982 100644
--- a/graphics/java/android/graphics/pdf/PdfEditor.java
+++ b/graphics/java/android/graphics/pdf/PdfEditor.java
@@ -25,7 +25,9 @@
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
+
import dalvik.system.CloseGuard;
+
import libcore.io.IoUtils;
import java.io.IOException;
@@ -37,6 +39,12 @@
*/
public final class PdfEditor {
+ /**
+ * Any call the native pdfium code has to be single threaded as the library does not support
+ * parallel use.
+ */
+ private static final Object sPdfiumLock = new Object();
+
private final CloseGuard mCloseGuard = CloseGuard.get();
private long mNativeDocument;
@@ -79,7 +87,7 @@
}
mInput = input;
- synchronized (PdfRenderer.sPdfiumLock) {
+ synchronized (sPdfiumLock) {
mNativeDocument = nativeOpen(mInput.getFd(), size);
try {
mPageCount = nativeGetPageCount(mNativeDocument);
@@ -112,7 +120,7 @@
throwIfClosed();
throwIfPageNotInDocument(pageIndex);
- synchronized (PdfRenderer.sPdfiumLock) {
+ synchronized (sPdfiumLock) {
mPageCount = nativeRemovePage(mNativeDocument, pageIndex);
}
}
@@ -138,12 +146,12 @@
Point size = new Point();
getPageSize(pageIndex, size);
- synchronized (PdfRenderer.sPdfiumLock) {
+ synchronized (sPdfiumLock) {
nativeSetTransformAndClip(mNativeDocument, pageIndex, transform.ni(),
0, 0, size.x, size.y);
}
} else {
- synchronized (PdfRenderer.sPdfiumLock) {
+ synchronized (sPdfiumLock) {
nativeSetTransformAndClip(mNativeDocument, pageIndex, transform.ni(),
clip.left, clip.top, clip.right, clip.bottom);
}
@@ -161,7 +169,7 @@
throwIfOutSizeNull(outSize);
throwIfPageNotInDocument(pageIndex);
- synchronized (PdfRenderer.sPdfiumLock) {
+ synchronized (sPdfiumLock) {
nativeGetPageSize(mNativeDocument, pageIndex, outSize);
}
}
@@ -177,7 +185,7 @@
throwIfOutMediaBoxNull(outMediaBox);
throwIfPageNotInDocument(pageIndex);
- synchronized (PdfRenderer.sPdfiumLock) {
+ synchronized (sPdfiumLock) {
return nativeGetPageMediaBox(mNativeDocument, pageIndex, outMediaBox);
}
}
@@ -193,7 +201,7 @@
throwIfMediaBoxNull(mediaBox);
throwIfPageNotInDocument(pageIndex);
- synchronized (PdfRenderer.sPdfiumLock) {
+ synchronized (sPdfiumLock) {
nativeSetPageMediaBox(mNativeDocument, pageIndex, mediaBox);
}
}
@@ -209,7 +217,7 @@
throwIfOutCropBoxNull(outCropBox);
throwIfPageNotInDocument(pageIndex);
- synchronized (PdfRenderer.sPdfiumLock) {
+ synchronized (sPdfiumLock) {
return nativeGetPageCropBox(mNativeDocument, pageIndex, outCropBox);
}
}
@@ -225,7 +233,7 @@
throwIfCropBoxNull(cropBox);
throwIfPageNotInDocument(pageIndex);
- synchronized (PdfRenderer.sPdfiumLock) {
+ synchronized (sPdfiumLock) {
nativeSetPageCropBox(mNativeDocument, pageIndex, cropBox);
}
}
@@ -238,7 +246,7 @@
public boolean shouldScaleForPrinting() {
throwIfClosed();
- synchronized (PdfRenderer.sPdfiumLock) {
+ synchronized (sPdfiumLock) {
return nativeScaleForPrinting(mNativeDocument);
}
}
@@ -255,7 +263,7 @@
try {
throwIfClosed();
- synchronized (PdfRenderer.sPdfiumLock) {
+ synchronized (sPdfiumLock) {
nativeWrite(mNativeDocument, output.getFd());
}
} finally {
@@ -287,7 +295,7 @@
private void doClose() {
if (mNativeDocument != 0) {
- synchronized (PdfRenderer.sPdfiumLock) {
+ synchronized (sPdfiumLock) {
nativeClose(mNativeDocument);
}
mNativeDocument = 0;
diff --git a/graphics/java/android/graphics/pdf/PdfRenderer.java b/graphics/java/android/graphics/pdf/PdfRenderer.java
deleted file mode 100644
index 4666963..0000000
--- a/graphics/java/android/graphics/pdf/PdfRenderer.java
+++ /dev/null
@@ -1,502 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.graphics.pdf;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.compat.annotation.UnsupportedAppUsage;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Matrix;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.Build;
-import android.os.ParcelFileDescriptor;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
-
-import com.android.internal.util.Preconditions;
-
-import dalvik.system.CloseGuard;
-
-import libcore.io.IoUtils;
-
-import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * <p>
- * This class enables rendering a PDF document. This class is not thread safe.
- * </p>
- * <p>
- * If you want to render a PDF, you create a renderer and for every page you want
- * to render, you open the page, render it, and close the page. After you are done
- * with rendering, you close the renderer. After the renderer is closed it should not
- * be used anymore. Note that the pages are rendered one by one, i.e. you can have
- * only a single page opened at any given time.
- * </p>
- * <p>
- * A typical use of the APIs to render a PDF looks like this:
- * </p>
- * <pre>
- * // create a new renderer
- * PdfRenderer renderer = new PdfRenderer(getSeekableFileDescriptor());
- *
- * // let us just render all pages
- * final int pageCount = renderer.getPageCount();
- * for (int i = 0; i < pageCount; i++) {
- * Page page = renderer.openPage(i);
- *
- * // say we render for showing on the screen
- * page.render(mBitmap, null, null, Page.RENDER_MODE_FOR_DISPLAY);
- *
- * // do stuff with the bitmap
- *
- * // close the page
- * page.close();
- * }
- *
- * // close the renderer
- * renderer.close();
- * </pre>
- *
- * <h3>Print preview and print output</h3>
- * <p>
- * If you are using this class to rasterize a PDF for printing or show a print
- * preview, it is recommended that you respect the following contract in order
- * to provide a consistent user experience when seeing a preview and printing,
- * i.e. the user sees a preview that is the same as the printout.
- * </p>
- * <ul>
- * <li>
- * Respect the property whether the document would like to be scaled for printing
- * as per {@link #shouldScaleForPrinting()}.
- * </li>
- * <li>
- * When scaling a document for printing the aspect ratio should be preserved.
- * </li>
- * <li>
- * Do not inset the content with any margins from the {@link android.print.PrintAttributes}
- * as the application is responsible to render it such that the margins are respected.
- * </li>
- * <li>
- * If document page size is greater than the printed media size the content should
- * be anchored to the upper left corner of the page for left-to-right locales and
- * top right corner for right-to-left locales.
- * </li>
- * </ul>
- *
- * @see #close()
- */
-public final class PdfRenderer implements AutoCloseable {
- /**
- * Any call the native pdfium code has to be single threaded as the library does not support
- * parallel use.
- */
- final static Object sPdfiumLock = new Object();
-
- private final CloseGuard mCloseGuard = CloseGuard.get();
-
- private final Point mTempPoint = new Point();
-
- private long mNativeDocument;
-
- private final int mPageCount;
-
- private ParcelFileDescriptor mInput;
-
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- private Page mCurrentPage;
-
- /** @hide */
- @IntDef({
- Page.RENDER_MODE_FOR_DISPLAY,
- Page.RENDER_MODE_FOR_PRINT
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface RenderMode {}
-
- /**
- * Creates a new instance.
- * <p>
- * <strong>Note:</strong> The provided file descriptor must be <strong>seekable</strong>,
- * i.e. its data being randomly accessed, e.g. pointing to a file.
- * </p>
- * <p>
- * <strong>Note:</strong> This class takes ownership of the passed in file descriptor
- * and is responsible for closing it when the renderer is closed.
- * </p>
- * <p>
- * If the file is from an untrusted source it is recommended to run the renderer in a separate,
- * isolated process with minimal permissions to limit the impact of security exploits.
- * </p>
- *
- * @param input Seekable file descriptor to read from.
- *
- * @throws java.io.IOException If an error occurs while reading the file.
- * @throws java.lang.SecurityException If the file requires a password or
- * the security scheme is not supported.
- */
- public PdfRenderer(@NonNull ParcelFileDescriptor input) throws IOException {
- if (input == null) {
- throw new NullPointerException("input cannot be null");
- }
-
- final long size;
- try {
- Os.lseek(input.getFileDescriptor(), 0, OsConstants.SEEK_SET);
- size = Os.fstat(input.getFileDescriptor()).st_size;
- } catch (ErrnoException ee) {
- throw new IllegalArgumentException("file descriptor not seekable");
- }
- mInput = input;
-
- synchronized (sPdfiumLock) {
- mNativeDocument = nativeCreate(mInput.getFd(), size);
- try {
- mPageCount = nativeGetPageCount(mNativeDocument);
- } catch (Throwable t) {
- nativeClose(mNativeDocument);
- mNativeDocument = 0;
- throw t;
- }
- }
-
- mCloseGuard.open("close");
- }
-
- /**
- * Closes this renderer. You should not use this instance
- * after this method is called.
- */
- public void close() {
- throwIfClosed();
- throwIfPageOpened();
- doClose();
- }
-
- /**
- * Gets the number of pages in the document.
- *
- * @return The page count.
- */
- public int getPageCount() {
- throwIfClosed();
- return mPageCount;
- }
-
- /**
- * Gets whether the document prefers to be scaled for printing.
- * You should take this info account if the document is rendered
- * for printing and the target media size differs from the page
- * size.
- *
- * @return If to scale the document.
- */
- public boolean shouldScaleForPrinting() {
- throwIfClosed();
-
- synchronized (sPdfiumLock) {
- return nativeScaleForPrinting(mNativeDocument);
- }
- }
-
- /**
- * Opens a page for rendering.
- *
- * @param index The page index.
- * @return A page that can be rendered.
- *
- * @see android.graphics.pdf.PdfRenderer.Page#close() PdfRenderer.Page.close()
- */
- public Page openPage(int index) {
- throwIfClosed();
- throwIfPageOpened();
- throwIfPageNotInDocument(index);
- mCurrentPage = new Page(index);
- return mCurrentPage;
- }
-
- @Override
- protected void finalize() throws Throwable {
- try {
- if (mCloseGuard != null) {
- mCloseGuard.warnIfOpen();
- }
-
- doClose();
- } finally {
- super.finalize();
- }
- }
-
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- private void doClose() {
- if (mCurrentPage != null) {
- mCurrentPage.close();
- mCurrentPage = null;
- }
-
- if (mNativeDocument != 0) {
- synchronized (sPdfiumLock) {
- nativeClose(mNativeDocument);
- }
- mNativeDocument = 0;
- }
-
- if (mInput != null) {
- IoUtils.closeQuietly(mInput);
- mInput = null;
- }
- mCloseGuard.close();
- }
-
- private void throwIfClosed() {
- if (mInput == null) {
- throw new IllegalStateException("Already closed");
- }
- }
-
- private void throwIfPageOpened() {
- if (mCurrentPage != null) {
- throw new IllegalStateException("Current page not closed");
- }
- }
-
- private void throwIfPageNotInDocument(int pageIndex) {
- if (pageIndex < 0 || pageIndex >= mPageCount) {
- throw new IllegalArgumentException("Invalid page index");
- }
- }
-
- /**
- * This class represents a PDF document page for rendering.
- */
- public final class Page implements AutoCloseable {
-
- private final CloseGuard mCloseGuard = CloseGuard.get();
-
- /**
- * Mode to render the content for display on a screen.
- */
- public static final int RENDER_MODE_FOR_DISPLAY = 1;
-
- /**
- * Mode to render the content for printing.
- */
- public static final int RENDER_MODE_FOR_PRINT = 2;
-
- private final int mIndex;
- private final int mWidth;
- private final int mHeight;
-
- private long mNativePage;
-
- private Page(int index) {
- Point size = mTempPoint;
- synchronized (sPdfiumLock) {
- mNativePage = nativeOpenPageAndGetSize(mNativeDocument, index, size);
- }
- mIndex = index;
- mWidth = size.x;
- mHeight = size.y;
- mCloseGuard.open("close");
- }
-
- /**
- * Gets the page index.
- *
- * @return The index.
- */
- public int getIndex() {
- return mIndex;
- }
-
- /**
- * Gets the page width in points (1/72").
- *
- * @return The width in points.
- */
- public int getWidth() {
- return mWidth;
- }
-
- /**
- * Gets the page height in points (1/72").
- *
- * @return The height in points.
- */
- public int getHeight() {
- return mHeight;
- }
-
- /**
- * Renders a page to a bitmap.
- * <p>
- * You may optionally specify a rectangular clip in the bitmap bounds. No rendering
- * outside the clip will be performed, hence it is your responsibility to initialize
- * the bitmap outside the clip.
- * </p>
- * <p>
- * You may optionally specify a matrix to transform the content from page coordinates
- * which are in points (1/72") to bitmap coordinates which are in pixels. If this
- * matrix is not provided this method will apply a transformation that will fit the
- * whole page to the destination clip if provided or the destination bitmap if no
- * clip is provided.
- * </p>
- * <p>
- * The clip and transformation are useful for implementing tile rendering where the
- * destination bitmap contains a portion of the image, for example when zooming.
- * Another useful application is for printing where the size of the bitmap holding
- * the page is too large and a client can render the page in stripes.
- * </p>
- * <p>
- * <strong>Note: </strong> The destination bitmap format must be
- * {@link Config#ARGB_8888 ARGB}.
- * </p>
- * <p>
- * <strong>Note: </strong> The optional transformation matrix must be affine as per
- * {@link android.graphics.Matrix#isAffine() Matrix.isAffine()}. Hence, you can specify
- * rotation, scaling, translation but not a perspective transformation.
- * </p>
- *
- * @param destination Destination bitmap to which to render.
- * @param destClip Optional clip in the bitmap bounds.
- * @param transform Optional transformation to apply when rendering.
- * @param renderMode The render mode.
- *
- * @see #RENDER_MODE_FOR_DISPLAY
- * @see #RENDER_MODE_FOR_PRINT
- */
- public void render(@NonNull Bitmap destination, @Nullable Rect destClip,
- @Nullable Matrix transform, @RenderMode int renderMode) {
- if (mNativePage == 0) {
- throw new NullPointerException();
- }
-
- destination = Preconditions.checkNotNull(destination, "bitmap null");
-
- if (destination.getConfig() != Config.ARGB_8888) {
- throw new IllegalArgumentException("Unsupported pixel format");
- }
-
- if (destClip != null) {
- if (destClip.left < 0 || destClip.top < 0
- || destClip.right > destination.getWidth()
- || destClip.bottom > destination.getHeight()) {
- throw new IllegalArgumentException("destBounds not in destination");
- }
- }
-
- if (transform != null && !transform.isAffine()) {
- throw new IllegalArgumentException("transform not affine");
- }
-
- if (renderMode != RENDER_MODE_FOR_PRINT && renderMode != RENDER_MODE_FOR_DISPLAY) {
- throw new IllegalArgumentException("Unsupported render mode");
- }
-
- if (renderMode == RENDER_MODE_FOR_PRINT && renderMode == RENDER_MODE_FOR_DISPLAY) {
- throw new IllegalArgumentException("Only single render mode supported");
- }
-
- final int contentLeft = (destClip != null) ? destClip.left : 0;
- final int contentTop = (destClip != null) ? destClip.top : 0;
- final int contentRight = (destClip != null) ? destClip.right
- : destination.getWidth();
- final int contentBottom = (destClip != null) ? destClip.bottom
- : destination.getHeight();
-
- // If transform is not set, stretch page to whole clipped area
- if (transform == null) {
- int clipWidth = contentRight - contentLeft;
- int clipHeight = contentBottom - contentTop;
-
- transform = new Matrix();
- transform.postScale((float)clipWidth / getWidth(),
- (float)clipHeight / getHeight());
- transform.postTranslate(contentLeft, contentTop);
- }
-
- // FIXME: This code is planned to be outside the UI rendering module, so it should not
- // be able to access native instances from Bitmap, Matrix, etc.
- final long transformPtr = transform.ni();
-
- synchronized (sPdfiumLock) {
- nativeRenderPage(mNativeDocument, mNativePage, destination.getNativeInstance(),
- contentLeft, contentTop, contentRight, contentBottom, transformPtr,
- renderMode);
- }
- }
-
- /**
- * Closes this page.
- *
- * @see android.graphics.pdf.PdfRenderer#openPage(int)
- */
- @Override
- public void close() {
- throwIfClosed();
- doClose();
- }
-
- @Override
- protected void finalize() throws Throwable {
- try {
- if (mCloseGuard != null) {
- mCloseGuard.warnIfOpen();
- }
-
- doClose();
- } finally {
- super.finalize();
- }
- }
-
- private void doClose() {
- if (mNativePage != 0) {
- synchronized (sPdfiumLock) {
- nativeClosePage(mNativePage);
- }
- mNativePage = 0;
- }
-
- mCloseGuard.close();
- mCurrentPage = null;
- }
-
- private void throwIfClosed() {
- if (mNativePage == 0) {
- throw new IllegalStateException("Already closed");
- }
- }
- }
-
- private static native long nativeCreate(int fd, long size);
- private static native void nativeClose(long documentPtr);
- private static native int nativeGetPageCount(long documentPtr);
- private static native boolean nativeScaleForPrinting(long documentPtr);
- private static native void nativeRenderPage(long documentPtr, long pagePtr, long bitmapHandle,
- int clipLeft, int clipTop, int clipRight, int clipBottom, long transformPtr,
- int renderMode);
- private static native long nativeOpenPageAndGetSize(long documentPtr, int pageIndex,
- Point outSize);
- private static native void nativeClosePage(long pagePtr);
-}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
index 88fd461..fa35b63 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
@@ -16,7 +16,7 @@
package androidx.window.common;
-import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN;
import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE;
@@ -69,14 +69,14 @@
* example is activated via public API and can be active in both the "open" and "half folded"
* device states.
*/
- private int mCurrentDeviceState = INVALID_DEVICE_STATE;
+ private int mCurrentDeviceState = INVALID_DEVICE_STATE_IDENTIFIER;
/**
* Base device state received via
* {@link DeviceStateManager.DeviceStateCallback#onBaseStateChanged(int)}.
* "Base" in this context means the "physical" state of the device.
*/
- private int mCurrentBaseDeviceState = INVALID_DEVICE_STATE;
+ private int mCurrentBaseDeviceState = INVALID_DEVICE_STATE_IDENTIFIER;
@NonNull
private final RawFoldingFeatureProducer mRawFoldSupplier;
@@ -177,7 +177,7 @@
if (hasListeners()) {
mRawFoldSupplier.addDataChangedCallback(this::notifyFoldingFeatureChange);
} else {
- mCurrentDeviceState = INVALID_DEVICE_STATE;
+ mCurrentDeviceState = INVALID_DEVICE_STATE_IDENTIFIER;
mRawFoldSupplier.removeDataChangedCallback(this::notifyFoldingFeatureChange);
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index b315f94..d31bf2a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -16,7 +16,7 @@
package androidx.window.extensions.area;
-import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
import android.app.Activity;
import android.content.Context;
@@ -79,7 +79,7 @@
private int mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE;
@GuardedBy("mLock")
- private int mCurrentDeviceState = INVALID_DEVICE_STATE;
+ private int mCurrentDeviceState = INVALID_DEVICE_STATE_IDENTIFIER;
@GuardedBy("mLock")
private int[] mCurrentSupportedDeviceStates;
@@ -143,7 +143,7 @@
mRearDisplayStatusListeners.add(consumer);
// If current device state is still invalid, the initial value has not been provided.
- if (mCurrentDeviceState == INVALID_DEVICE_STATE) {
+ if (mCurrentDeviceState == INVALID_DEVICE_STATE_IDENTIFIER) {
return;
}
consumer.accept(getCurrentRearDisplayModeStatus());
@@ -308,7 +308,7 @@
mRearDisplayPresentationStatusListeners.add(consumer);
// If current device state is still invalid, the initial value has not been provided
- if (mCurrentDeviceState == INVALID_DEVICE_STATE) {
+ if (mCurrentDeviceState == INVALID_DEVICE_STATE_IDENTIFIER) {
return;
}
@WindowAreaStatus int currentStatus = getCurrentRearDisplayPresentationModeStatus();
@@ -467,7 +467,7 @@
@GuardedBy("mLock")
private int getCurrentRearDisplayModeStatus() {
- if (mRearDisplayState == INVALID_DEVICE_STATE) {
+ if (mRearDisplayState == INVALID_DEVICE_STATE_IDENTIFIER) {
return WindowAreaComponent.STATUS_UNSUPPORTED;
}
@@ -495,7 +495,7 @@
@GuardedBy("mLock")
private void updateRearDisplayStatusListeners(@WindowAreaStatus int windowAreaStatus) {
- if (mRearDisplayState == INVALID_DEVICE_STATE) {
+ if (mRearDisplayState == INVALID_DEVICE_STATE_IDENTIFIER) {
return;
}
synchronized (mLock) {
@@ -507,7 +507,7 @@
@GuardedBy("mLock")
private int getCurrentRearDisplayPresentationModeStatus() {
- if (mConcurrentDisplayState == INVALID_DEVICE_STATE) {
+ if (mConcurrentDisplayState == INVALID_DEVICE_STATE_IDENTIFIER) {
return WindowAreaComponent.STATUS_UNSUPPORTED;
}
@@ -530,7 +530,7 @@
@GuardedBy("mLock")
private void updateRearDisplayPresentationStatusListeners(
@WindowAreaStatus int windowAreaStatus) {
- if (mConcurrentDisplayState == INVALID_DEVICE_STATE) {
+ if (mConcurrentDisplayState == INVALID_DEVICE_STATE_IDENTIFIER) {
return;
}
RearDisplayPresentationStatus consumerValue = new RearDisplayPresentationStatus(
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
index 9cd14fca..e422198 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
@@ -28,6 +28,7 @@
import androidx.test.filters.SmallTest
import com.android.wm.shell.R
import com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT
+import com.android.wm.shell.common.bubbles.BubbleBarLocation
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors.directExecutor
import org.junit.Before
@@ -486,6 +487,32 @@
positioner.screenRect.width() - paddings[0] - paddings[2])
}
+ @Test
+ fun testIsBubbleBarOnLeft_defaultsToRight() {
+ positioner.bubbleBarLocation = BubbleBarLocation.DEFAULT
+ assertThat(positioner.isBubbleBarOnLeft).isFalse()
+
+ // Check that left and right return expected position
+ positioner.bubbleBarLocation = BubbleBarLocation.LEFT
+ assertThat(positioner.isBubbleBarOnLeft).isTrue()
+ positioner.bubbleBarLocation = BubbleBarLocation.RIGHT
+ assertThat(positioner.isBubbleBarOnLeft).isFalse()
+ }
+
+ @Test
+ fun testIsBubbleBarOnLeft_rtlEnabled_defaultsToLeft() {
+ positioner.update(defaultDeviceConfig.copy(isRtl = true))
+
+ positioner.bubbleBarLocation = BubbleBarLocation.DEFAULT
+ assertThat(positioner.isBubbleBarOnLeft).isTrue()
+
+ // Check that left and right return expected position
+ positioner.bubbleBarLocation = BubbleBarLocation.LEFT
+ assertThat(positioner.isBubbleBarOnLeft).isTrue()
+ positioner.bubbleBarLocation = BubbleBarLocation.RIGHT
+ assertThat(positioner.isBubbleBarOnLeft).isFalse()
+ }
+
private val defaultYPosition: Float
/**
* Calculates the Y position bubbles should be placed based on the config. Based on the
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index a883e08..b54f9cf 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -109,7 +109,7 @@
<string name="app_icon_text" msgid="2823268023931811747">"Icône de l\'application"</string>
<string name="fullscreen_text" msgid="1162316685217676079">"Plein écran"</string>
<string name="desktop_text" msgid="1077633567027630454">"Mode Bureau"</string>
- <string name="split_screen_text" msgid="1396336058129570886">"Écran partagé"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Écran divisé"</string>
<string name="more_button_text" msgid="3655388105592893530">"Plus"</string>
<string name="float_button_text" msgid="9221657008391364581">"Flottant"</string>
<string name="select_text" msgid="5139083974039906583">"Sélectionner"</string>
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index a541c59..c68b0be 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -148,4 +148,7 @@
<!-- Whether pointer pilfer is required to start back animation. -->
<bool name="config_backAnimationRequiresPointerPilfer">true</bool>
+
+ <!-- Whether desktop mode is supported on the current device -->
+ <bool name="config_isDesktopModeSupported">false</bool>
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 539832e..d44033c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -523,8 +523,8 @@
/**
* Whether we should use jump cut for the change transition.
* This normally happens when opening a new secondary with the existing primary using a
- * different split layout. This can be complicated, like from horizontal to vertical split with
- * new split pairs.
+ * different split layout (ratio or direction). This can be complicated, like from horizontal to
+ * vertical split with new split pairs.
* Uses a jump cut animation to simplify.
*/
private boolean shouldUseJumpCutForChangeTransition(@NonNull TransitionInfo info) {
@@ -553,8 +553,8 @@
}
// Check if the transition contains both opening and closing windows.
- boolean hasOpeningWindow = false;
- boolean hasClosingWindow = false;
+ final List<TransitionInfo.Change> openChanges = new ArrayList<>();
+ final List<TransitionInfo.Change> closeChanges = new ArrayList<>();
for (TransitionInfo.Change change : info.getChanges()) {
if (changingChanges.contains(change)) {
continue;
@@ -564,10 +564,30 @@
// No-op if it will be covered by the changing parent window.
continue;
}
- hasOpeningWindow |= TransitionUtil.isOpeningType(change.getMode());
- hasClosingWindow |= TransitionUtil.isClosingType(change.getMode());
+ if (TransitionUtil.isOpeningType(change.getMode())) {
+ openChanges.add(change);
+ } else if (TransitionUtil.isClosingType(change.getMode())) {
+ closeChanges.add(change);
+ }
}
- return hasOpeningWindow && hasClosingWindow;
+ if (openChanges.isEmpty() || closeChanges.isEmpty()) {
+ // Only skip if the transition contains both open and close.
+ return false;
+ }
+ if (changingChanges.size() != 1 || openChanges.size() != 1 || closeChanges.size() != 1) {
+ // Skip when there are too many windows involved.
+ return true;
+ }
+ final TransitionInfo.Change changingChange = changingChanges.get(0);
+ final TransitionInfo.Change openChange = openChanges.get(0);
+ final TransitionInfo.Change closeChange = closeChanges.get(0);
+ if (changingChange.getStartAbsBounds().equals(openChange.getEndAbsBounds())
+ && changingChange.getEndAbsBounds().equals(closeChange.getStartAbsBounds())) {
+ // Don't skip if the transition is a simple shifting without split direction or ratio
+ // change. For example, A|B -> B|C.
+ return false;
+ }
+ return true;
}
/** Updates the changes to end states in {@code startTransaction} for jump cut animation. */
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 96aaf02..4455a3c 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
@@ -103,6 +103,7 @@
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.onehanded.OneHandedController;
@@ -708,6 +709,30 @@
return mBubbleProperties.isBubbleBarEnabled() && mBubblePositioner.isLargeScreen();
}
+ /**
+ * Returns current {@link BubbleBarLocation} if bubble bar is being used.
+ * Otherwise returns <code>null</code>
+ */
+ @Nullable
+ public BubbleBarLocation getBubbleBarLocation() {
+ if (canShowAsBubbleBar()) {
+ return mBubblePositioner.getBubbleBarLocation();
+ }
+ return null;
+ }
+
+ /**
+ * Update bubble bar location and trigger and update to listeners
+ */
+ public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
+ if (canShowAsBubbleBar()) {
+ mBubblePositioner.setBubbleBarLocation(bubbleBarLocation);
+ BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
+ bubbleBarUpdate.bubbleBarLocation = bubbleBarLocation;
+ mBubbleStateListener.onBubbleStateChange(bubbleBarUpdate);
+ }
+ }
+
/** Whether this userId belongs to the current user. */
private boolean isCurrentProfile(int userId) {
return userId == UserHandle.USER_ALL
@@ -1179,7 +1204,7 @@
*/
@VisibleForTesting
public void expandStackAndSelectBubbleFromLauncher(String key, Rect bubbleBarBounds) {
- mBubblePositioner.setBubbleBarPosition(bubbleBarBounds);
+ mBubblePositioner.setBubbleBarBounds(bubbleBarBounds);
if (BubbleOverflow.KEY.equals(key)) {
mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow());
@@ -1234,12 +1259,14 @@
* Expands and selects a bubble based on the provided {@link BubbleEntry}. If no bubble
* exists for this entry, and it is able to bubble, a new bubble will be created.
*
- * This is the method to use when opening a bubble via a notification or in a state where
+ * <p>This is the method to use when opening a bubble via a notification or in a state where
* the device might not be unlocked.
*
* @param entry the entry to use for the bubble.
*/
public void expandStackAndSelectBubble(BubbleEntry entry) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "opening bubble from notification key=%s mIsStatusBarShade=%b",
+ entry.getKey(), mIsStatusBarShade);
if (mIsStatusBarShade) {
mNotifEntryToExpandOnShadeUnlock = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 6c2f925..61f0ed2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -165,7 +165,7 @@
* used when {@link BubbleController#isShowingAsBubbleBar()} is true.
*/
BubbleBarUpdate getInitialState() {
- BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
+ BubbleBarUpdate bubbleBarUpdate = BubbleBarUpdate.createInitialState();
bubbleBarUpdate.shouldShowEducation = shouldShowEducation;
for (int i = 0; i < bubbles.size(); i++) {
bubbleBarUpdate.currentBubbleList.add(bubbles.get(i).asBubbleBarBubble());
@@ -255,7 +255,9 @@
* Returns a bubble bar update populated with the current list of active bubbles.
*/
public BubbleBarUpdate getInitialStateForBubbleBar() {
- return mStateChange.getInitialState();
+ BubbleBarUpdate initialState = mStateChange.getInitialState();
+ initialState.bubbleBarLocation = mPositioner.getBubbleBarLocation();
+ return initialState;
}
public void setSuppressionChangedListener(Bubbles.BubbleMetadataFlagListener listener) {
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 a5853d6..b215b61 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
@@ -32,6 +32,7 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconNormalizer;
import com.android.wm.shell.R;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
/**
* Keeps track of display size, configuration, and specific bubble sizes. One place for all
@@ -95,6 +96,7 @@
private PointF mRestingStackPosition;
private boolean mShowingInBubbleBar;
+ private BubbleBarLocation mBubbleBarLocation = BubbleBarLocation.DEFAULT;
private final Rect mBubbleBarBounds = new Rect();
public BubblePositioner(Context context, WindowManager windowManager) {
@@ -797,14 +799,36 @@
mShowingInBubbleBar = showingInBubbleBar;
}
+ public void setBubbleBarLocation(BubbleBarLocation location) {
+ mBubbleBarLocation = location;
+ }
+
+ public BubbleBarLocation getBubbleBarLocation() {
+ return mBubbleBarLocation;
+ }
+
+ /**
+ * @return <code>true</code> when bubble bar is on the left and <code>false</code> when on right
+ */
+ public boolean isBubbleBarOnLeft() {
+ return mBubbleBarLocation.isOnLeft(mDeviceConfig.isRtl());
+ }
+
/**
* Sets the position of the bubble bar in display coordinates.
*/
- public void setBubbleBarPosition(Rect bubbleBarBounds) {
+ public void setBubbleBarBounds(Rect bubbleBarBounds) {
mBubbleBarBounds.set(bubbleBarBounds);
}
/**
+ * Returns the display coordinates of the bubble bar.
+ */
+ public Rect getBubbleBarBounds() {
+ return mBubbleBarBounds;
+ }
+
+ /**
* How wide the expanded view should be when showing from the bubble bar.
*/
public int getExpandedViewWidthForBubbleBar(boolean isOverflow) {
@@ -831,11 +855,4 @@
public int getBubbleBarExpandedViewPadding() {
return mExpandedViewPadding;
}
-
- /**
- * Returns the display coordinates of the bubble bar.
- */
- public Rect getBubbleBarBounds() {
- return mBubbleBarBounds;
- }
}
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 474430e..6524c96 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
@@ -449,17 +449,21 @@
@Override
public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target,
- @NonNull MagnetizedObject draggedObject) {
- if (draggedObject.getUnderlyingObject() instanceof View view) {
+ @NonNull MagnetizedObject<?> draggedObject) {
+ Object underlyingObject = draggedObject.getUnderlyingObject();
+ if (underlyingObject instanceof View) {
+ View view = (View) underlyingObject;
animateDismissBubble(view, true);
}
}
@Override
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
- @NonNull MagnetizedObject draggedObject,
+ @NonNull MagnetizedObject<?> draggedObject,
float velX, float velY, boolean wasFlungOut) {
- if (draggedObject.getUnderlyingObject() instanceof View view) {
+ Object underlyingObject = draggedObject.getUnderlyingObject();
+ if (underlyingObject instanceof View) {
+ View view = (View) underlyingObject;
animateDismissBubble(view, false);
if (wasFlungOut) {
@@ -474,7 +478,9 @@
@Override
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target,
@NonNull MagnetizedObject<?> draggedObject) {
- if (draggedObject.getUnderlyingObject() instanceof View view) {
+ Object underlyingObject = draggedObject.getUnderlyingObject();
+ if (underlyingObject instanceof View) {
+ View view = (View) underlyingObject;
mExpandedAnimationController.dismissDraggedOutBubble(
view /* bubble */,
mDismissView.getHeight() /* translationYBy */,
@@ -2474,11 +2480,12 @@
// Let the expanded animation controller know that it shouldn't animate child adds/reorders
// since we're about to animate collapsed.
mExpandedAnimationController.notifyPreparingToCollapse();
-
+ final PointF collapsePosition = mStackAnimationController
+ .getStackPositionAlongNearestHorizontalEdge();
updateOverflowDotVisibility(false /* expanding */);
final Runnable collapseBackToStack = () ->
mExpandedAnimationController.collapseBackToStack(
- mStackAnimationController.getStackPositionAlongNearestHorizontalEdge(),
+ collapsePosition,
/* fadeBubblesDuringCollapse= */ mRemovingLastBubbleWhileExpanded,
() -> {
mBubbleContainer.setActiveController(mStackAnimationController);
@@ -2501,7 +2508,8 @@
}
mExpandedViewAnimationController.reset();
};
- mExpandedViewAnimationController.animateCollapse(collapseBackToStack, after);
+ mExpandedViewAnimationController.animateCollapse(collapseBackToStack, after,
+ collapsePosition);
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
// When the animation completes, we should no longer be showing the content.
// This won't actually update content visibility immediately, if we are currently
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java
index 8a33780..4175529 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java
@@ -15,6 +15,8 @@
*/
package com.android.wm.shell.bubbles.animation;
+import android.graphics.PointF;
+
import com.android.wm.shell.bubbles.BubbleExpandedView;
/**
@@ -55,8 +57,9 @@
* @param startStackCollapse runnable that is triggered when bubbles can start moving back to
* their collapsed location
* @param after runnable to run after animation is complete
+ * @param collapsePosition the position on screen the stack will collapse to
*/
- void animateCollapse(Runnable startStackCollapse, Runnable after);
+ void animateCollapse(Runnable startStackCollapse, Runnable after, PointF collapsePosition);
/**
* Animate the view back to fully expanded state.
@@ -69,6 +72,22 @@
void animateForImeVisibilityChange(boolean visible);
/**
+ * Whether this controller should also animate the expansion for the bubble
+ */
+ boolean shouldAnimateExpansion();
+
+ /**
+ * Animate the expansion of the bubble.
+ *
+ * @param startDelayMillis how long to delay starting the expansion animation
+ * @param after runnable to run after the animation is complete
+ * @param collapsePosition the position on screen the stack will collapse to (and expand from)
+ * @param bubblePosition the position of the bubble on screen that the view is associated with
+ */
+ void animateExpansion(long startDelayMillis, Runnable after, PointF collapsePosition,
+ PointF bubblePosition);
+
+ /**
* Reset the view to fully expanded state
*/
void reset();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
index e43609f..aa4129a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
@@ -28,6 +28,7 @@
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
+import android.graphics.PointF;
import android.view.HapticFeedbackConstants;
import android.view.ViewConfiguration;
@@ -187,9 +188,11 @@
}
@Override
- public void animateCollapse(Runnable startStackCollapse, Runnable after) {
- ProtoLog.d(WM_SHELL_BUBBLES, "expandedView animate collapse swipeVel=%f minFlingVel=%d",
- mSwipeUpVelocity, mMinFlingVelocity);
+ public void animateCollapse(Runnable startStackCollapse, Runnable after,
+ PointF collapsePosition) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "expandedView animate collapse swipeVel=%f minFlingVel=%d"
+ + " collapsePosition=%f,%f", mSwipeUpVelocity, mMinFlingVelocity,
+ collapsePosition.x, collapsePosition.y);
if (mExpandedView != null) {
// Mark it as animating immediately to avoid updates to the view before animation starts
mExpandedView.setAnimating(true);
@@ -274,6 +277,17 @@
}
@Override
+ public boolean shouldAnimateExpansion() {
+ return false;
+ }
+
+ @Override
+ public void animateExpansion(long startDelayMillis, Runnable after, PointF collapsePosition,
+ PointF bubblePosition) {
+ // TODO - animate
+ }
+
+ @Override
public void reset() {
ProtoLog.d(WM_SHELL_BUBBLES, "reset expandedView collapsed state");
if (mExpandedView == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index 8946f41..9eb9632 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -477,7 +477,7 @@
private Point getExpandedViewRestPosition(Size size) {
final int padding = mPositioner.getBubbleBarExpandedViewPadding();
Point point = new Point();
- if (mLayerView.isOnLeft()) {
+ if (mPositioner.isBubbleBarOnLeft()) {
point.x = mPositioner.getInsets().left + padding;
} else {
point.x = mPositioner.getAvailableRect().width() - size.getWidth() - padding;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index 7d37d60..056598b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -19,6 +19,8 @@
import android.annotation.SuppressLint
import android.view.MotionEvent
import android.view.View
+import com.android.wm.shell.bubbles.BubblePositioner
+import com.android.wm.shell.common.bubbles.BubbleBarLocation
import com.android.wm.shell.common.bubbles.DismissView
import com.android.wm.shell.common.bubbles.RelativeTouchListener
import com.android.wm.shell.common.magnetictarget.MagnetizedObject
@@ -29,7 +31,8 @@
private val expandedView: BubbleBarExpandedView,
private val dismissView: DismissView,
private val animationHelper: BubbleBarAnimationHelper,
- private val onDismissed: () -> Unit
+ private val bubblePositioner: BubblePositioner,
+ private val dragListener: DragListener
) {
var isStuckToDismiss: Boolean = false
@@ -45,11 +48,11 @@
magnetizedExpandedView.magnetListener = MagnetListener()
magnetizedExpandedView.animateStuckToTarget =
{
- target: MagnetizedObject.MagneticTarget,
- _: Float,
- _: Float,
- _: Boolean,
- after: (() -> Unit)? ->
+ target: MagnetizedObject.MagneticTarget,
+ _: Float,
+ _: Float,
+ _: Boolean,
+ after: (() -> Unit)? ->
animationHelper.animateIntoTarget(target, after)
}
@@ -73,13 +76,34 @@
}
}
+ /** Listener to receive callback about dragging events */
+ interface DragListener {
+ /**
+ * Bubble bar [BubbleBarLocation] has changed as a result of dragging the expanded view.
+ *
+ * Triggered when drag gesture passes the middle of the screen and before touch up.
+ * Can be triggered multiple times per gesture.
+ *
+ * @param location new location of the bubble bar as a result of the ongoing drag operation
+ */
+ fun onLocationChanged(location: BubbleBarLocation)
+
+ /** Expanded view has been released in the dismiss target */
+ fun onReleasedInDismiss()
+ }
+
private inner class HandleDragListener : RelativeTouchListener() {
private var isMoving = false
+ private var screenCenterX: Int = -1
+ private var isOnLeft = false
override fun onDown(v: View, ev: MotionEvent): Boolean {
// While animating, don't allow new touch events
- return !expandedView.isAnimating
+ if (expandedView.isAnimating) return false
+ screenCenterX = bubblePositioner.screenRect.centerX()
+ isOnLeft = bubblePositioner.isBubbleBarOnLeft
+ return true
}
override fun onMove(
@@ -97,6 +121,14 @@
expandedView.translationX = expandedViewInitialTranslationX + dx
expandedView.translationY = expandedViewInitialTranslationY + dy
dismissView.show()
+
+ if (isOnLeft && ev.rawX > screenCenterX) {
+ isOnLeft = false
+ dragListener.onLocationChanged(BubbleBarLocation.RIGHT)
+ } else if (!isOnLeft && ev.rawX < screenCenterX) {
+ isOnLeft = true
+ dragListener.onLocationChanged(BubbleBarLocation.LEFT)
+ }
}
override fun onUp(
@@ -113,6 +145,7 @@
}
override fun onCancel(v: View, ev: MotionEvent, viewInitialX: Float, viewInitialY: Float) {
+ isStuckToDismiss = false
finishDrag()
}
@@ -127,30 +160,29 @@
private inner class MagnetListener : MagnetizedObject.MagnetListener {
override fun onStuckToTarget(
- target: MagnetizedObject.MagneticTarget,
- draggedObject: MagnetizedObject<*>
+ target: MagnetizedObject.MagneticTarget,
+ draggedObject: MagnetizedObject<*>
) {
isStuckToDismiss = true
}
override fun onUnstuckFromTarget(
- target: MagnetizedObject.MagneticTarget,
- draggedObject: MagnetizedObject<*>,
- velX: Float,
- velY: Float,
- wasFlungOut: Boolean
+ target: MagnetizedObject.MagneticTarget,
+ draggedObject: MagnetizedObject<*>,
+ velX: Float,
+ velY: Float,
+ wasFlungOut: Boolean
) {
isStuckToDismiss = false
animationHelper.animateUnstuckFromDismissView(target)
}
override fun onReleasedInTarget(
- target: MagnetizedObject.MagneticTarget,
- draggedObject: MagnetizedObject<*>
+ target: MagnetizedObject.MagneticTarget,
+ draggedObject: MagnetizedObject<*>
) {
- onDismissed()
+ dragListener.onReleasedInDismiss()
dismissView.hide()
}
}
}
-
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 42799d9..3fb9f63 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
@@ -33,6 +33,8 @@
import android.view.WindowManager;
import android.widget.FrameLayout;
+import androidx.annotation.NonNull;
+
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.Bubble;
import com.android.wm.shell.bubbles.BubbleController;
@@ -42,6 +44,8 @@
import com.android.wm.shell.bubbles.BubbleViewProvider;
import com.android.wm.shell.bubbles.DeviceConfig;
import com.android.wm.shell.bubbles.DismissViewUtils;
+import com.android.wm.shell.bubbles.bar.BubbleBarExpandedViewDragController.DragListener;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
import com.android.wm.shell.common.bubbles.DismissView;
import kotlin.Unit;
@@ -155,12 +159,6 @@
return mIsExpanded;
}
- // TODO(b/313661121) - when dragging is implemented, check user setting first
- /** Whether the expanded view is positioned on the left or right side of the screen. */
- public boolean isOnLeft() {
- return getLayoutDirection() == LAYOUT_DIRECTION_RTL;
- }
-
/** Shows the expanded view of the provided bubble. */
public void showExpandedView(BubbleViewProvider b) {
BubbleBarExpandedView expandedView = b.getBubbleBarExpandedView();
@@ -207,15 +205,23 @@
}
});
+ DragListener dragListener = new DragListener() {
+ @Override
+ public void onLocationChanged(@NonNull BubbleBarLocation location) {
+ mBubbleController.setBubbleBarLocation(location);
+ }
+
+ @Override
+ public void onReleasedInDismiss() {
+ mBubbleController.dismissBubble(mExpandedBubble.getKey(), DISMISS_USER_GESTURE);
+ }
+ };
mDragController = new BubbleBarExpandedViewDragController(
mExpandedView,
mDismissView,
mAnimationHelper,
- () -> {
- mBubbleController.dismissBubble(mExpandedBubble.getKey(),
- DISMISS_USER_GESTURE);
- return Unit.INSTANCE;
- });
+ mPositioner,
+ dragListener);
addView(mExpandedView, new LayoutParams(width, height, Gravity.LEFT));
}
@@ -352,7 +358,7 @@
lp.width = width;
lp.height = height;
mExpandedView.setLayoutParams(lp);
- if (isOnLeft()) {
+ if (mPositioner.isBubbleBarOnLeft()) {
mExpandedView.setX(mPositioner.getInsets().left + padding);
} else {
mExpandedView.setX(mPositioner.getAvailableRect().width() - width - padding);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
index 4c0281d..e261d92 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.common;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL;
+
import android.annotation.BinderThread;
import android.annotation.NonNull;
import android.os.RemoteException;
@@ -26,6 +28,7 @@
import android.window.WindowContainerTransactionCallback;
import android.window.WindowOrganizer;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.transition.LegacyTransitions;
import java.util.ArrayList;
@@ -204,6 +207,7 @@
@Override
public void onTransactionReady(int id,
@NonNull SurfaceControl.Transaction t) {
+ ProtoLog.v(WM_SHELL, "SyncTransactionQueue.onTransactionReady(): syncId=%d", id);
mMainExecutor.execute(() -> {
synchronized (mQueue) {
if (mId != id) {
@@ -223,6 +227,8 @@
Slog.e(TAG, "Error sending callback to legacy transition: " + mId, e);
}
} else {
+ ProtoLog.v(WM_SHELL,
+ "SyncTransactionQueue.onTransactionReady(): syncId=%d apply", id);
t.apply();
t.close();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.kt
new file mode 100644
index 0000000..f0bdfde
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.common.bubbles
+
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * The location of the bubble bar.
+ */
+enum class BubbleBarLocation : Parcelable {
+ /**
+ * Place bubble bar at the default location for the chosen system language.
+ * If an RTL language is used, it is on the left. Otherwise on the right.
+ */
+ DEFAULT,
+ /** Default bubble bar location is overridden. Place bubble bar on the left. */
+ LEFT,
+ /** Default bubble bar location is overridden. Place bubble bar on the right. */
+ RIGHT;
+
+ /**
+ * Returns whether bubble bar is pinned to the left edge or right edge.
+ */
+ fun isOnLeft(isRtl: Boolean): Boolean {
+ if (this == DEFAULT) {
+ return isRtl
+ }
+ return this == LEFT
+ }
+
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ dest.writeString(name)
+ }
+
+ companion object {
+ @JvmField
+ val CREATOR = object : Parcelable.Creator<BubbleBarLocation> {
+ override fun createFromParcel(parcel: Parcel): BubbleBarLocation {
+ return parcel.readString()?.let { valueOf(it) } ?: DEFAULT
+ }
+
+ override fun newArray(size: Int) = arrayOfNulls<BubbleBarLocation>(size)
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
index fc627a8..e5f6c37 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
@@ -33,6 +33,7 @@
public static final String BUNDLE_KEY = "update";
+ public final boolean initialState;
public boolean expandedChanged;
public boolean expanded;
public boolean shouldShowEducation;
@@ -46,6 +47,8 @@
public String suppressedBubbleKey;
@Nullable
public String unsupressedBubbleKey;
+ @Nullable
+ public BubbleBarLocation bubbleBarLocation;
// This is only populated if bubbles have been removed.
public List<RemovedBubble> removedBubbles = new ArrayList<>();
@@ -56,10 +59,17 @@
// This is only populated the first time a listener is connected so it gets the current state.
public List<BubbleInfo> currentBubbleList = new ArrayList<>();
+
public BubbleBarUpdate() {
+ this(false);
+ }
+
+ private BubbleBarUpdate(boolean initialState) {
+ this.initialState = initialState;
}
public BubbleBarUpdate(Parcel parcel) {
+ initialState = parcel.readBoolean();
expandedChanged = parcel.readBoolean();
expanded = parcel.readBoolean();
shouldShowEducation = parcel.readBoolean();
@@ -75,6 +85,8 @@
parcel.readStringList(bubbleKeysInOrder);
currentBubbleList = parcel.readParcelableList(new ArrayList<>(),
BubbleInfo.class.getClassLoader());
+ bubbleBarLocation = parcel.readParcelable(BubbleBarLocation.class.getClassLoader(),
+ BubbleBarLocation.class);
}
/**
@@ -89,12 +101,15 @@
|| !bubbleKeysInOrder.isEmpty()
|| suppressedBubbleKey != null
|| unsupressedBubbleKey != null
- || !currentBubbleList.isEmpty();
+ || !currentBubbleList.isEmpty()
+ || bubbleBarLocation != null;
}
@Override
public String toString() {
- return "BubbleBarUpdate{ expandedChanged=" + expandedChanged
+ return "BubbleBarUpdate{"
+ + " initialState=" + initialState
+ + " expandedChanged=" + expandedChanged
+ " expanded=" + expanded
+ " selectedBubbleKey=" + selectedBubbleKey
+ " shouldShowEducation=" + shouldShowEducation
@@ -105,6 +120,7 @@
+ " removedBubbles=" + removedBubbles
+ " bubbles=" + bubbleKeysInOrder
+ " currentBubbleList=" + currentBubbleList
+ + " bubbleBarLocation=" + bubbleBarLocation
+ " }";
}
@@ -115,6 +131,7 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeBoolean(initialState);
parcel.writeBoolean(expandedChanged);
parcel.writeBoolean(expanded);
parcel.writeBoolean(shouldShowEducation);
@@ -126,6 +143,16 @@
parcel.writeParcelableList(removedBubbles, flags);
parcel.writeStringList(bubbleKeysInOrder);
parcel.writeParcelableList(currentBubbleList, flags);
+ parcel.writeParcelable(bubbleBarLocation, flags);
+ }
+
+ /**
+ * Create update for initial set of values.
+ * <p>
+ * Used when bubble bar is newly created.
+ */
+ public static BubbleBarUpdate createInitialState() {
+ return new BubbleBarUpdate(true);
}
@NonNull
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 8b2ec0a..8d489e1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -846,8 +846,10 @@
static ShellController provideShellController(Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
+ DisplayInsetsController displayInsetsController,
@ShellMainThread ShellExecutor mainExecutor) {
- return new ShellController(context, shellInit, shellCommandHandler, mainExecutor);
+ return new ShellController(context, shellInit, shellCommandHandler,
+ displayInsetsController, mainExecutor);
}
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index fb3c35b..04f0f44 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -57,6 +57,8 @@
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
import com.android.wm.shell.dagger.pip.PipModule;
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
+import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -509,6 +511,7 @@
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
DragToDesktopTransitionHandler dragToDesktopTransitionHandler,
@DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
+ DesktopModeLoggerTransitionObserver desktopModeLoggerTransitionObserver,
LaunchAdjacentController launchAdjacentController,
RecentsTransitionHandler recentsTransitionHandler,
MultiInstanceHelper multiInstanceHelper,
@@ -518,7 +521,8 @@
displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
dragAndDropController, transitions, enterDesktopTransitionHandler,
exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler,
- dragToDesktopTransitionHandler, desktopModeTaskRepository, launchAdjacentController,
+ dragToDesktopTransitionHandler, desktopModeTaskRepository,
+ desktopModeLoggerTransitionObserver, launchAdjacentController,
recentsTransitionHandler, multiInstanceHelper, mainExecutor);
}
@@ -562,6 +566,22 @@
return new DesktopModeTaskRepository();
}
+ @WMSingleton
+ @Provides
+ static DesktopModeLoggerTransitionObserver provideDesktopModeLoggerTransitionObserver(
+ ShellInit shellInit,
+ Transitions transitions,
+ DesktopModeEventLogger desktopModeEventLogger) {
+ return new DesktopModeLoggerTransitionObserver(
+ shellInit, transitions, desktopModeEventLogger);
+ }
+
+ @WMSingleton
+ @Provides
+ static DesktopModeEventLogger provideDesktopModeEventLogger() {
+ return new DesktopModeEventLogger();
+ }
+
//
// Drag and drop
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index 1071d72..838603f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -53,4 +53,7 @@
/** Called when requested to go to fullscreen from the current focused desktop app. */
void moveFocusedTaskToFullscreen(int displayId);
+
+ /** Called when requested to go to split screen from the current focused desktop app. */
+ void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
new file mode 100644
index 0000000..a10c7c0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityTaskManager.INVALID_TASK_ID
+import android.app.TaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.os.IBinder
+import android.util.SparseArray
+import android.view.SurfaceControl
+import android.view.WindowManager
+import android.window.TransitionInfo
+import androidx.annotation.VisibleForTesting
+import androidx.core.util.containsKey
+import androidx.core.util.forEach
+import androidx.core.util.isEmpty
+import androidx.core.util.isNotEmpty
+import androidx.core.util.plus
+import androidx.core.util.putAll
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.InstanceIdSequence
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.shared.TransitionUtil
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.util.KtProtoLog
+
+/**
+ * A [Transitions.TransitionObserver] that observes transitions and the proposed changes to log
+ * appropriate desktop mode session log events. This observes transitions related to desktop mode
+ * and other transitions that originate both within and outside shell.
+ */
+class DesktopModeLoggerTransitionObserver(
+ shellInit: ShellInit,
+ private val transitions: Transitions,
+ private val desktopModeEventLogger: DesktopModeEventLogger
+) : Transitions.TransitionObserver {
+
+ private val idSequence: InstanceIdSequence by lazy { InstanceIdSequence(Int.MAX_VALUE) }
+
+ init {
+ if (Transitions.ENABLE_SHELL_TRANSITIONS && DesktopModeStatus.isEnabled()) {
+ shellInit.addInitCallback(this::onInit, this)
+ }
+ }
+
+ // A sparse array of visible freeform tasks and taskInfos
+ private val visibleFreeformTaskInfos: SparseArray<TaskInfo> = SparseArray()
+
+ // Caching the taskInfos to handle canceled recents animations, if we identify that the recents
+ // animation was cancelled, we restore these tasks to calculate the post-Transition state
+ private val tasksSavedForRecents: SparseArray<TaskInfo> = SparseArray()
+
+ // The instanceId for the current logging session
+ private var loggerInstanceId: InstanceId? = null
+
+ private val isSessionActive: Boolean
+ get() = loggerInstanceId != null
+
+ private fun setSessionInactive() {
+ loggerInstanceId = null
+ }
+
+ fun onInit() {
+ transitions.registerObserver(this)
+ }
+
+ override fun onTransitionReady(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction
+ ) {
+ // this was a new recents animation
+ if (info.isRecentsTransition() && tasksSavedForRecents.isEmpty()) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Recents animation running, saving tasks for later"
+ )
+ // TODO (b/326391303) - avoid logging session exit if we can identify a cancelled
+ // recents animation
+
+ // when recents animation is running, all freeform tasks are sent TO_BACK temporarily
+ // if the user ends up at home, we need to update the visible freeform tasks
+ // if the user cancels the animation, the subsequent transition is NONE
+ // if the user opens a new task, the subsequent transition is OPEN with flag
+ tasksSavedForRecents.putAll(visibleFreeformTaskInfos)
+ }
+
+ // figure out what the new state of freeform tasks would be post transition
+ var postTransitionVisibleFreeformTasks = getPostTransitionVisibleFreeformTaskInfos(info)
+
+ // A canceled recents animation is followed by a TRANSIT_NONE transition with no flags, if
+ // that's the case, we might have accidentally logged a session exit and would need to
+ // revaluate again. Add all the tasks back.
+ // This will start a new desktop mode session.
+ if (
+ info.type == WindowManager.TRANSIT_NONE &&
+ info.flags == 0 &&
+ tasksSavedForRecents.isNotEmpty()
+ ) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Canceled recents animation, restoring tasks"
+ )
+ // restore saved tasks in the updated set and clear for next use
+ postTransitionVisibleFreeformTasks += tasksSavedForRecents
+ tasksSavedForRecents.clear()
+ }
+
+ // identify if we need to log any changes and update the state of visible freeform tasks
+ identifyLogEventAndUpdateState(
+ transitionInfo = info,
+ preTransitionVisibleFreeformTasks = visibleFreeformTaskInfos,
+ postTransitionVisibleFreeformTasks = postTransitionVisibleFreeformTasks
+ )
+ }
+
+ override fun onTransitionStarting(transition: IBinder) {}
+
+ override fun onTransitionMerged(merged: IBinder, playing: IBinder) {}
+
+ override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {}
+
+ private fun getPostTransitionVisibleFreeformTaskInfos(
+ info: TransitionInfo
+ ): SparseArray<TaskInfo> {
+ // device is sleeping, so no task will be visible anymore
+ if (info.type == WindowManager.TRANSIT_SLEEP) {
+ return SparseArray()
+ }
+
+ // filter changes involving freeform tasks or tasks that were cached in previous state
+ val changesToFreeformWindows =
+ info.changes
+ .filter { it.taskInfo != null && it.requireTaskInfo().taskId != INVALID_TASK_ID }
+ .filter {
+ it.requireTaskInfo().isFreeformWindow() ||
+ visibleFreeformTaskInfos.containsKey(it.requireTaskInfo().taskId)
+ }
+
+ val postTransitionFreeformTasks: SparseArray<TaskInfo> = SparseArray()
+ // start off by adding all existing tasks
+ postTransitionFreeformTasks.putAll(visibleFreeformTaskInfos)
+
+ // the combined set of taskInfos we are interested in this transition change
+ for (change in changesToFreeformWindows) {
+ val taskInfo = change.requireTaskInfo()
+
+ // check if this task existed as freeform window in previous cached state and it's now
+ // changing window modes
+ if (
+ visibleFreeformTaskInfos.containsKey(taskInfo.taskId) &&
+ visibleFreeformTaskInfos.get(taskInfo.taskId).isFreeformWindow() &&
+ !taskInfo.isFreeformWindow()
+ ) {
+ postTransitionFreeformTasks.remove(taskInfo.taskId)
+ // no need to evaluate new visibility of this task, since it's no longer a freeform
+ // window
+ continue
+ }
+
+ // check if the task is visible after this change, otherwise remove it
+ if (isTaskVisibleAfterChange(change)) {
+ postTransitionFreeformTasks.put(taskInfo.taskId, taskInfo)
+ } else {
+ postTransitionFreeformTasks.remove(taskInfo.taskId)
+ }
+ }
+
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: taskInfo map after processing changes %s",
+ postTransitionFreeformTasks.size()
+ )
+
+ return postTransitionFreeformTasks
+ }
+
+ /**
+ * Look at the [TransitionInfo.Change] and figure out if this task will be visible after this
+ * change is processed
+ */
+ private fun isTaskVisibleAfterChange(change: TransitionInfo.Change): Boolean =
+ when {
+ TransitionUtil.isOpeningType(change.mode) -> true
+ TransitionUtil.isClosingType(change.mode) -> false
+ // change mode TRANSIT_CHANGE is only for visible to visible transitions
+ change.mode == WindowManager.TRANSIT_CHANGE -> true
+ else -> false
+ }
+
+ /**
+ * Log the appropriate log event based on the new state of TasksInfos and previously cached
+ * state and update it
+ */
+ private fun identifyLogEventAndUpdateState(
+ transitionInfo: TransitionInfo,
+ preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
+ postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>
+ ) {
+ if (
+ postTransitionVisibleFreeformTasks.isEmpty() &&
+ preTransitionVisibleFreeformTasks.isNotEmpty() &&
+ isSessionActive
+ ) {
+ // Sessions is finishing, log task updates followed by an exit event
+ identifyAndLogTaskUpdates(
+ loggerInstanceId!!.id,
+ preTransitionVisibleFreeformTasks,
+ postTransitionVisibleFreeformTasks
+ )
+
+ desktopModeEventLogger.logSessionExit(
+ loggerInstanceId!!.id,
+ getExitReason(transitionInfo)
+ )
+
+ setSessionInactive()
+ } else if (
+ postTransitionVisibleFreeformTasks.isNotEmpty() &&
+ preTransitionVisibleFreeformTasks.isEmpty() &&
+ !isSessionActive
+ ) {
+ // Session is starting, log enter event followed by task updates
+ loggerInstanceId = idSequence.newInstanceId()
+ desktopModeEventLogger.logSessionEnter(
+ loggerInstanceId!!.id,
+ getEnterReason(transitionInfo)
+ )
+
+ identifyAndLogTaskUpdates(
+ loggerInstanceId!!.id,
+ preTransitionVisibleFreeformTasks,
+ postTransitionVisibleFreeformTasks
+ )
+ } else if (isSessionActive) {
+ // Session is neither starting, nor finishing, log task updates if there are any
+ identifyAndLogTaskUpdates(
+ loggerInstanceId!!.id,
+ preTransitionVisibleFreeformTasks,
+ postTransitionVisibleFreeformTasks
+ )
+ }
+
+ // update the state to the new version
+ visibleFreeformTaskInfos.clear()
+ visibleFreeformTaskInfos.putAll(postTransitionVisibleFreeformTasks)
+ }
+
+ // TODO(b/326231724) - Add logging around taskInfoChanges Updates
+ /** Compare the old and new state of taskInfos and identify and log the changes */
+ private fun identifyAndLogTaskUpdates(
+ sessionId: Int,
+ preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
+ postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>
+ ) {
+ // find new tasks that were added
+ postTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
+ if (!preTransitionVisibleFreeformTasks.containsKey(taskId)) {
+ desktopModeEventLogger.logTaskAdded(sessionId, buildTaskUpdateForTask(taskInfo))
+ }
+ }
+
+ // find old tasks that were removed
+ preTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
+ if (!postTransitionVisibleFreeformTasks.containsKey(taskId)) {
+ desktopModeEventLogger.logTaskRemoved(sessionId, buildTaskUpdateForTask(taskInfo))
+ }
+ }
+ }
+
+ // TODO(b/326231724: figure out how to get taskWidth and taskHeight from TaskInfo
+ private fun buildTaskUpdateForTask(taskInfo: TaskInfo): TaskUpdate {
+ val taskUpdate = TaskUpdate(taskInfo.taskId, taskInfo.userId)
+ // add task x, y if available
+ taskInfo.positionInParent?.let { taskUpdate.copy(taskX = it.x, taskY = it.y) }
+
+ return taskUpdate
+ }
+
+ /** Get [EnterReason] for this session enter */
+ private fun getEnterReason(transitionInfo: TransitionInfo): EnterReason {
+ // TODO(b/326231756) - Add support for missing enter reasons
+ return when (transitionInfo.type) {
+ WindowManager.TRANSIT_WAKE -> EnterReason.SCREEN_ON
+ Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP -> EnterReason.APP_HANDLE_DRAG
+ Transitions.TRANSIT_MOVE_TO_DESKTOP -> EnterReason.APP_HANDLE_MENU_BUTTON
+ WindowManager.TRANSIT_OPEN -> EnterReason.APP_FREEFORM_INTENT
+ else -> EnterReason.UNKNOWN_ENTER
+ }
+ }
+
+ /** Get [ExitReason] for this session exit */
+ private fun getExitReason(transitionInfo: TransitionInfo): ExitReason {
+ // TODO(b/326231756) - Add support for missing exit reasons
+ return when {
+ transitionInfo.type == WindowManager.TRANSIT_SLEEP -> ExitReason.SCREEN_OFF
+ transitionInfo.type == WindowManager.TRANSIT_CLOSE -> ExitReason.TASK_FINISHED
+ transitionInfo.type == Transitions.TRANSIT_EXIT_DESKTOP_MODE -> ExitReason.DRAG_TO_EXIT
+ transitionInfo.isRecentsTransition() -> ExitReason.RETURN_HOME_OR_OVERVIEW
+ else -> ExitReason.UNKNOWN_EXIT
+ }
+ }
+
+ /** Adds tasks to the saved copy of freeform taskId, taskInfo. Only used for testing. */
+ @VisibleForTesting
+ fun addTaskInfosToCachedMap(taskInfo: TaskInfo) {
+ visibleFreeformTaskInfos.set(taskInfo.taskId, taskInfo)
+ }
+
+ @VisibleForTesting fun getLoggerSessionId(): Int? = loggerInstanceId?.id
+
+ @VisibleForTesting
+ fun setLoggerSessionId(id: Int) {
+ loggerInstanceId = InstanceId.fakeInstanceId(id)
+ }
+
+ private fun TransitionInfo.Change.requireTaskInfo(): RunningTaskInfo {
+ return this.taskInfo ?: throw IllegalStateException("Expected TaskInfo in the Change")
+ }
+
+ private fun TaskInfo.isFreeformWindow(): Boolean {
+ return this.windowingMode == WINDOWING_MODE_FREEFORM
+ }
+
+ private fun TransitionInfo.isRecentsTransition(): Boolean {
+ return this.type == WindowManager.TRANSIT_TO_FRONT &&
+ this.flags == WindowManager.TRANSIT_FLAG_IS_RECENTS
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index 22ba708..494d893 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -16,9 +16,13 @@
package com.android.wm.shell.desktopmode;
+import android.annotation.NonNull;
+import android.content.Context;
import android.os.SystemProperties;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.window.flags.Flags;
+import com.android.wm.shell.R;
/**
* Constants for desktop mode feature
@@ -67,6 +71,12 @@
"persist.wm.debug.desktop_use_rounded_corners", true);
/**
+ * Flag to indicate whether to restrict desktop mode to supported devices.
+ */
+ private static final boolean ENFORCE_DEVICE_RESTRICTIONS = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_mode_enforce_device_restrictions", true);
+
+ /**
* Return {@code true} if desktop windowing is enabled
*/
public static boolean isEnabled() {
@@ -104,4 +114,27 @@
public static boolean useRoundedCorners() {
return USE_ROUNDED_CORNERS;
}
+
+ /**
+ * Return {@code true} if desktop mode should be restricted to supported devices.
+ */
+ @VisibleForTesting
+ public static boolean enforceDeviceRestrictions() {
+ return ENFORCE_DEVICE_RESTRICTIONS;
+ }
+
+ /**
+ * Return {@code true} if the current device supports desktop mode.
+ */
+ @VisibleForTesting
+ public static boolean isDesktopModeSupported(@NonNull Context context) {
+ return context.getResources().getBoolean(R.bool.config_isDesktopModeSupported);
+ }
+
+ /**
+ * Return {@code true} if desktop mode can be entered on the current device.
+ */
+ public static boolean canEnterDesktopMode(@NonNull Context context) {
+ return !enforceDeviceRestrictions() || isDesktopModeSupported(context);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index fb0ed15..6a3c8d2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -138,9 +138,9 @@
@WindowConfiguration.WindowingMode int windowingMode, int captionHeight) {
final Region region = new Region();
int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
- ? 2 * layout.stableInsets().top
- : mContext.getResources().getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height);
+ ? mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height)
+ : 2 * layout.stableInsets().top;
// A thin, short Rect at the top of the screen.
if (windowingMode == WINDOWING_MODE_FREEFORM) {
int fromFreeformWidth = mContext.getResources().getDimensionPixelSize(
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 654409f..4a3130f 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
@@ -62,6 +62,7 @@
import com.android.wm.shell.common.annotations.ExternalThread
import com.android.wm.shell.common.annotations.ShellMainThread
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
+import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
import com.android.wm.shell.draganddrop.DragAndDropController
@@ -77,6 +78,7 @@
import com.android.wm.shell.transition.OneShotRemoteHandler
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.KtProtoLog
+import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
import java.io.PrintWriter
@@ -101,6 +103,7 @@
ToggleResizeDesktopTaskTransitionHandler,
private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler,
private val desktopModeTaskRepository: DesktopModeTaskRepository,
+ private val desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver,
private val launchAdjacentController: LaunchAdjacentController,
private val recentsTransitionHandler: RecentsTransitionHandler,
private val multiInstanceHelper: MultiInstanceHelper,
@@ -305,6 +308,12 @@
task: RunningTaskInfo,
wct: WindowContainerTransaction = WindowContainerTransaction()
) {
+ if (!DesktopModeStatus.canEnterDesktopMode(context)) {
+ KtProtoLog.w(
+ WM_SHELL_DESKTOP_MODE, "DesktopTasksController: Cannot enter desktop, " +
+ "display does not meet minimum size requirements")
+ return
+ }
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: moveToDesktop taskId=%d",
@@ -382,14 +391,8 @@
/** Enter fullscreen by moving the focused freeform task in given `displayId` to fullscreen. */
fun enterFullscreen(displayId: Int) {
- if (DesktopModeStatus.isEnabled()) {
- shellTaskOrganizer
- .getRunningTasks(displayId)
- .find { taskInfo ->
- taskInfo.isFocused && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
- }
- ?.let { moveToFullscreenWithAnimation(it, it.positionInParent) }
- }
+ getFocusedFreeformTask(displayId)
+ ?.let { moveToFullscreenWithAnimation(it, it.positionInParent) }
}
/** Move a desktop app to split screen. */
@@ -870,12 +873,28 @@
wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
}
+ /** Enter split by using the focused desktop task in given `displayId`. */
+ fun enterSplit(
+ displayId: Int,
+ leftOrTop: Boolean
+ ) {
+ getFocusedFreeformTask(displayId)?.let { requestSplit(it, leftOrTop) }
+ }
+
+ private fun getFocusedFreeformTask(displayId: Int): RunningTaskInfo? {
+ return shellTaskOrganizer.getRunningTasks(displayId)
+ .find { taskInfo -> taskInfo.isFocused &&
+ taskInfo.windowingMode == WINDOWING_MODE_FREEFORM }
+ }
+
/**
* Requests a task be transitioned from desktop to split select. Applies needed windowing
* changes if this transition is enabled.
*/
+ @JvmOverloads
fun requestSplit(
- taskInfo: RunningTaskInfo
+ taskInfo: RunningTaskInfo,
+ leftOrTop: Boolean = false,
) {
val windowingMode = taskInfo.windowingMode
if (windowingMode == WINDOWING_MODE_FULLSCREEN || windowingMode == WINDOWING_MODE_FREEFORM
@@ -883,7 +902,8 @@
val wct = WindowContainerTransaction()
addMoveToSplitChanges(wct, taskInfo)
splitScreenController.requestEnterSplitSelect(taskInfo, wct,
- SPLIT_POSITION_BOTTOM_OR_RIGHT, taskInfo.configuration.windowConfiguration.bounds)
+ if (leftOrTop) SPLIT_POSITION_TOP_OR_LEFT else SPLIT_POSITION_BOTTOM_OR_RIGHT,
+ taskInfo.configuration.windowConfiguration.bounds)
}
}
@@ -941,7 +961,8 @@
}
/**
- * Perform checks required on drag end. Move to fullscreen if drag ends in status bar area.
+ * Perform checks required on drag end. If indicator indicates a windowing mode change, perform
+ * that change. Otherwise, ensure bounds are up to date.
*
* @param taskInfo the task being dragged.
* @param position position of surface when drag ends.
@@ -952,7 +973,8 @@
taskInfo: RunningTaskInfo,
position: Point,
inputCoordinate: PointF,
- taskBounds: Rect
+ taskBounds: Rect,
+ validDragArea: Rect
) {
if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) {
return
@@ -975,10 +997,21 @@
releaseVisualIndicator()
snapToHalfScreen(taskInfo, SnapPosition.RIGHT)
}
- DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR,
DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR -> {
+ // If task bounds are outside valid drag area, snap them inward and perform a
+ // transaction to set bounds.
+ if (DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(
+ taskBounds, validDragArea)) {
+ val wct = WindowContainerTransaction()
+ wct.setBounds(taskInfo.token, taskBounds)
+ transitions.startTransition(TRANSIT_CHANGE, wct, null)
+ }
releaseVisualIndicator()
}
+ DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> {
+ throw IllegalArgumentException("Should not be receiving TO_DESKTOP_INDICATOR for " +
+ "a freeform task.")
+ }
}
// A freeform drag-move ended, remove the indicator immediately.
releaseVisualIndicator()
@@ -1134,6 +1167,12 @@
this@DesktopTasksController.enterFullscreen(displayId)
}
}
+
+ override fun moveFocusedTaskToStageSplit(displayId: Int, leftOrTop: Boolean) {
+ mainExecutor.execute {
+ this@DesktopTasksController.enterSplit(displayId, leftOrTop)
+ }
+ }
}
/** The interface for calls from outside the host process. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index e73a850..4c69cc3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -32,13 +32,16 @@
import androidx.annotation.BinderThread;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.pip.IPip;
import com.android.wm.shell.common.pip.IPipAnimationListener;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
@@ -57,15 +60,40 @@
DisplayController.OnDisplaysChangedListener, RemoteCallable<PipController> {
private static final String TAG = PipController.class.getSimpleName();
- private Context mContext;
- private ShellController mShellController;
- private DisplayController mDisplayController;
- private DisplayInsetsController mDisplayInsetsController;
- private PipBoundsState mPipBoundsState;
- private PipBoundsAlgorithm mPipBoundsAlgorithm;
- private PipDisplayLayoutState mPipDisplayLayoutState;
- private PipScheduler mPipScheduler;
- private ShellExecutor mMainExecutor;
+ private final Context mContext;
+ private final ShellController mShellController;
+ private final DisplayController mDisplayController;
+ private final DisplayInsetsController mDisplayInsetsController;
+ private final PipBoundsState mPipBoundsState;
+ private final PipBoundsAlgorithm mPipBoundsAlgorithm;
+ private final PipDisplayLayoutState mPipDisplayLayoutState;
+ private final PipScheduler mPipScheduler;
+ private final ShellExecutor mMainExecutor;
+
+ // Wrapper for making Binder calls into PiP animation listener hosted in launcher's Recents.
+ private PipAnimationListener mPipRecentsAnimationListener;
+
+ @VisibleForTesting
+ interface PipAnimationListener {
+ /**
+ * Notifies the listener that the Pip animation is started.
+ */
+ void onPipAnimationStarted();
+
+ /**
+ * Notifies the listener about PiP resource dimensions changed.
+ * Listener can expect an immediate callback the first time they attach.
+ *
+ * @param cornerRadius the pixel value of the corner radius, zero means it's disabled.
+ * @param shadowRadius the pixel value of the shadow radius, zero means it's disabled.
+ */
+ void onPipResourceDimensionsChanged(int cornerRadius, int shadowRadius);
+
+ /**
+ * Notifies the listener that user leaves PiP by tapping on the expand button.
+ */
+ void onExpandPip();
+ }
private PipController(Context context,
ShellInit shellInit,
@@ -92,39 +120,6 @@
}
}
- @Override
- public Context getContext() {
- return mContext;
- }
-
- @Override
- public ShellExecutor getRemoteCallExecutor() {
- return mMainExecutor;
- }
-
- private void onInit() {
- // Ensure that we have the display info in case we get calls to update the bounds before the
- // listener calls back
- mPipDisplayLayoutState.setDisplayId(mContext.getDisplayId());
- DisplayLayout layout = new DisplayLayout(mContext, mContext.getDisplay());
- mPipDisplayLayoutState.setDisplayLayout(layout);
-
- mShellController.addConfigurationChangeListener(this);
- mDisplayController.addDisplayWindowListener(this);
- mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
- new DisplayInsetsController.OnInsetsChangedListener() {
- @Override
- public void insetsChanged(InsetsState insetsState) {
- onDisplayChanged(mDisplayController
- .getDisplayLayout(mPipDisplayLayoutState.getDisplayId()));
- }
- });
-
- // Allow other outside processes to bind to PiP controller using the key below.
- mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP,
- this::createExternalInterface, this);
- }
-
/**
* Instantiates {@link PipController}, returns {@code null} if the feature not supported.
*/
@@ -148,20 +143,70 @@
pipScheduler, mainExecutor);
}
+ private void onInit() {
+ // Ensure that we have the display info in case we get calls to update the bounds before the
+ // listener calls back
+ mPipDisplayLayoutState.setDisplayId(mContext.getDisplayId());
+ DisplayLayout layout = new DisplayLayout(mContext, mContext.getDisplay());
+ mPipDisplayLayoutState.setDisplayLayout(layout);
+
+ mDisplayController.addDisplayWindowListener(this);
+ mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
+ new DisplayInsetsController.OnInsetsChangedListener() {
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ onDisplayChanged(mDisplayController
+ .getDisplayLayout(mPipDisplayLayoutState.getDisplayId()));
+ }
+ });
+
+ // Allow other outside processes to bind to PiP controller using the key below.
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP,
+ this::createExternalInterface, this);
+ mShellController.addConfigurationChangeListener(this);
+ }
+
private ExternalInterfaceBinder createExternalInterface() {
return new IPipImpl(this);
}
+ //
+ // RemoteCallable implementations
+ //
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
+ }
+
+ //
+ // ConfigurationChangeListener implementations
+ //
+
@Override
public void onConfigurationChanged(Configuration newConfiguration) {
mPipDisplayLayoutState.onConfigurationChanged();
}
@Override
+ public void onDensityOrFontScaleChanged() {
+ onPipResourceDimensionsChanged();
+ }
+
+ @Override
public void onThemeChanged() {
onDisplayChanged(new DisplayLayout(mContext, mContext.getDisplay()));
}
+ //
+ // DisplayController.OnDisplaysChangedListener implementations
+ //
+
@Override
public void onDisplayAdded(int displayId) {
if (displayId != mPipDisplayLayoutState.getDisplayId()) {
@@ -182,6 +227,10 @@
mPipDisplayLayoutState.setDisplayLayout(layout);
}
+ //
+ // IPip Binder stub helpers
+ //
+
private Rect getSwipePipToHomeBounds(ComponentName componentName, ActivityInfo activityInfo,
PictureInPictureParams pictureInPictureParams,
int launcherRotation, Rect hotseatKeepClearArea) {
@@ -197,18 +246,56 @@
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"onSwipePipToHomeAnimationStart: %s", componentName);
mPipScheduler.setInSwipePipToHomeTransition(true);
+ mPipRecentsAnimationListener.onPipAnimationStarted();
// TODO: cache the overlay if provided for reparenting later.
}
+ //
+ // IPipAnimationListener Binder proxy helpers
+ //
+
+ private void setPipRecentsAnimationListener(PipAnimationListener pipAnimationListener) {
+ mPipRecentsAnimationListener = pipAnimationListener;
+ onPipResourceDimensionsChanged();
+ }
+
+ private void onPipResourceDimensionsChanged() {
+ if (mPipRecentsAnimationListener != null) {
+ mPipRecentsAnimationListener.onPipResourceDimensionsChanged(
+ mContext.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius),
+ mContext.getResources().getDimensionPixelSize(R.dimen.pip_shadow_radius));
+ }
+ }
+
/**
* The interface for calls from outside the host process.
*/
@BinderThread
private static class IPipImpl extends IPip.Stub implements ExternalInterfaceBinder {
private PipController mController;
+ private final SingleInstanceRemoteListener<PipController, IPipAnimationListener> mListener;
+ private final PipAnimationListener mPipAnimationListener = new PipAnimationListener() {
+ @Override
+ public void onPipAnimationStarted() {
+ mListener.call(l -> l.onPipAnimationStarted());
+ }
+
+ @Override
+ public void onPipResourceDimensionsChanged(int cornerRadius, int shadowRadius) {
+ mListener.call(l -> l.onPipResourceDimensionsChanged(cornerRadius, shadowRadius));
+ }
+
+ @Override
+ public void onExpandPip() {
+ mListener.call(l -> l.onExpandPip());
+ }
+ };
IPipImpl(PipController controller) {
mController = controller;
+ mListener = new SingleInstanceRemoteListener<>(mController,
+ (cntrl) -> cntrl.setPipRecentsAnimationListener(mPipAnimationListener),
+ (cntrl) -> cntrl.setPipRecentsAnimationListener(null));
}
/**
@@ -217,6 +304,7 @@
@Override
public void invalidate() {
mController = null;
+ mListener.unregister();
}
@Override
@@ -257,7 +345,14 @@
@Override
public void setPipAnimationListener(IPipAnimationListener listener) {
- // TODO: set a proper animation listener to update the Launcher state as needed.
+ executeRemoteCallWithTaskPermission(mController, "setPipAnimationListener",
+ (controller) -> {
+ if (listener != null) {
+ mListener.register(listener);
+ } else {
+ mListener.unregister();
+ }
+ });
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index 895c793..6665013 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -172,7 +172,7 @@
}
void setInSwipePipToHomeTransition(boolean inSwipePipToHome) {
- mInSwipePipToHomeTransition = true;
+ mInSwipePipToHomeTransition = inSwipePipToHome;
}
boolean isInSwipePipToHomeTransition() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index dfb0475..d15da4a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -226,7 +226,26 @@
// cache the PiP task token and leash
mPipScheduler.setPipTaskToken(mPipTaskToken);
+ SurfaceControl pipLeash = pipChange.getLeash();
+ PictureInPictureParams params = pipChange.getTaskInfo().pictureInPictureParams;
+ Rect srcRectHint = params.getSourceRectHint();
+ Rect destinationBounds = pipChange.getEndAbsBounds();
+
+ if (PipBoundsAlgorithm.isSourceRectHintValidForEnterPip(srcRectHint, destinationBounds)) {
+ float scale = (float) destinationBounds.width() / srcRectHint.width();
+ startTransaction.setWindowCrop(pipLeash, srcRectHint);
+ startTransaction.setPosition(pipLeash,
+ destinationBounds.left - srcRectHint.left * scale,
+ destinationBounds.top - srcRectHint.top * scale);
+
+ // Reset the scale in case we are in the multi-activity case.
+ // TO_FRONT transition already scales down the task in single-activity case, but
+ // in multi-activity case, reparenting yields new reset scales coming from pinned task.
+ startTransaction.setScale(pipLeash, scale, scale);
+ } else {
+ // TODO(b/325481148): handle the case with invalid srcRectHint (using overlay).
+ }
startTransaction.apply();
finishCallback.onTransitionFinished(null);
return true;
@@ -303,6 +322,7 @@
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.movePipActivityToPinnedRootTask(pipTask.token, entryBounds);
+ wct.deferConfigToTransitionEnd(pipTask.token);
return wct;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index b5ea1b1..235456c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -418,6 +418,8 @@
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.start", mInstanceId);
if (mListener == null || mTransition == null) {
+ Slog.e(TAG, "Missing listener or transition, hasListener=" + (mListener != null) +
+ " hasTransition=" + (mTransition != null));
cleanUp();
return false;
}
@@ -531,21 +533,31 @@
// Put into the "below" layer space.
t.setLayer(change.getLeash(), layer);
mOpeningTasks.add(new TaskState(change, null /* leash */));
+ } else {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ " unhandled root taskId=%d", taskInfo.taskId);
}
} else if (TransitionUtil.isDividerBar(change)) {
final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
belowLayers - i, info, t, mLeashMap);
// Add this as a app and we will separate them on launcher side by window type.
apps.add(target);
+ } else {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ " unhandled change taskId=%d",
+ taskInfo != null ? taskInfo.taskId : -1);
}
}
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+ "Applying transaction=%d", t.getId());
t.apply();
Bundle b = new Bundle(1 /*capacity*/);
b.putParcelable(KEY_EXTRA_SPLIT_BOUNDS,
mRecentTasksController.getSplitBoundsForTaskId(closingSplitTaskId));
try {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- "[%d] RecentsController.start: calling onAnimationStart", mInstanceId);
+ "[%d] RecentsController.start: calling onAnimationStart with %d apps",
+ mInstanceId, apps.size());
mListener.onAnimationStart(this,
apps.toArray(new RemoteAnimationTarget[apps.size()]),
wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 952e2d4..86c8f04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -436,7 +436,11 @@
}
public void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
- mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
+ if (ENABLE_SHELL_TRANSITIONS) {
+ mStageCoordinator.dismissSplitScreen(toTopTaskId, exitReason);
+ } else {
+ mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
+ }
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
index 7f16c5e..af11ebc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.splitscreen;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_UNKNOWN;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -45,6 +46,8 @@
return runSetSideStagePosition(args, pw);
case "switchSplitPosition":
return runSwitchSplitPosition();
+ case "exitSplitScreen":
+ return runExitSplitScreen(args, pw);
default:
pw.println("Invalid command: " + args[0]);
return false;
@@ -91,6 +94,17 @@
return true;
}
+ private boolean runExitSplitScreen(String[] args, PrintWriter pw) {
+ if (args.length < 2) {
+ // First argument is the action name.
+ pw.println("Error: task id should be provided as arguments");
+ return false;
+ }
+ final int taskId = Integer.parseInt(args[1]);
+ mController.exitSplitScreen(taskId, EXIT_REASON_UNKNOWN);
+ return true;
+ }
+
@Override
public void printShellCommandHelp(PrintWriter pw, String prefix) {
pw.println(prefix + "moveToSideStage <taskId> <SideStagePosition>");
@@ -101,5 +115,7 @@
pw.println(prefix + " Sets the position of the side-stage.");
pw.println(prefix + "switchSplitPosition");
pw.println(prefix + " Reverses the split.");
+ pw.println(prefix + "exitSplitScreen <taskId>");
+ pw.println(prefix + " Exits split screen and leaves the provided split task on top.");
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 7650444..36368df9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -29,6 +29,7 @@
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -820,14 +821,15 @@
void startIntents(PendingIntent pendingIntent1, Intent fillInIntent1,
@Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
- PendingIntent pendingIntent2, Intent fillInIntent2,
+ @Nullable PendingIntent pendingIntent2, Intent fillInIntent2,
@Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2,
@SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
"startIntents: intent1=%s intent2=%s position=%d snapPosition=%d",
- pendingIntent1.getIntent(), pendingIntent2.getIntent(), splitPosition,
- snapPosition);
+ pendingIntent1.getIntent(),
+ (pendingIntent2 != null ? pendingIntent2.getIntent() : "null"),
+ splitPosition, snapPosition);
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (pendingIntent2 == null) {
options1 = options1 != null ? options1 : new Bundle();
@@ -1449,6 +1451,7 @@
mExitSplitScreenOnHide = exitSplitScreenOnHide;
}
+ /** Exits split screen with legacy transition */
void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: topTaskId=%d reason=%s active=%b",
toTopTaskId, exitReasonToString(exitReason), mMainStage.isActive());
@@ -1468,6 +1471,7 @@
applyExitSplitScreen(childrenToTop, wct, exitReason);
}
+ /** Exits split screen with legacy transition */
private void exitSplitScreen(@Nullable StageTaskListener childrenToTop,
@ExitReason int exitReason) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: mainStageToTop=%b reason=%s active=%b",
@@ -1545,6 +1549,14 @@
}
}
+ void dismissSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
+ if (!mMainStage.isActive()) return;
+ final int stage = getStageOfTask(toTopTaskId);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareExitSplitScreen(stage, wct);
+ mSplitTransitions.startDismissTransition(wct, this, stage, exitReason);
+ }
+
/**
* Overridden by child classes.
*/
@@ -1610,6 +1622,8 @@
// User has used a keyboard shortcut to go back to fullscreen from split
case EXIT_REASON_DESKTOP_MODE:
// One of the children enters desktop mode
+ case EXIT_REASON_UNKNOWN:
+ // Unknown reason
return true;
default:
return false;
@@ -2775,7 +2789,7 @@
+ " with " + taskInfo.taskId + " before startAnimation().");
record.addRecord(stage, true, taskInfo.taskId);
}
- } else if (isClosingType(change.getMode())) {
+ } else if (change.getMode() == TRANSIT_CLOSE) {
if (stage.containsTask(taskInfo.taskId)) {
record.addRecord(stage, false, taskInfo.taskId);
Log.w(TAG, "Expected onTaskVanished on " + stage + " to have been called"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index da2965c..2b12a22 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -251,9 +251,14 @@
final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
? windowInfo.targetActivityInfo
: taskInfo.topActivityInfo;
+ final boolean isEdgeToEdgeEnforced = PhoneWindow.isEdgeToEdgeEnforced(
+ activityInfo.applicationInfo, false /* local */, a);
+ if (isEdgeToEdgeEnforced) {
+ params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
+ }
params.layoutInDisplayCutoutMode = a.getInt(
R.styleable.Window_windowLayoutInDisplayCutoutMode,
- PhoneWindow.isEdgeToEdgeEnforced(activityInfo.applicationInfo, false /* local */, a)
+ isEdgeToEdgeEnforced
? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
: params.layoutInDisplayCutoutMode);
params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
index 31fc98b..da3aa4a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
@@ -461,25 +461,6 @@
a.recycle();
}
- // Reset the system bar color which set by splash screen, make it align to the app.
- void clearSystemBarColor() {
- if (mRootView == null || !mRootView.isAttachedToWindow()) {
- return;
- }
- if (mRootView.getLayoutParams() instanceof WindowManager.LayoutParams) {
- final WindowManager.LayoutParams lp =
- (WindowManager.LayoutParams) mRootView.getLayoutParams();
- if (mDrawsSystemBarBackgrounds) {
- lp.flags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
- } else {
- lp.flags &= ~WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
- }
- mRootView.setLayoutParams(lp);
- }
- mRootView.getWindowInsetsController().setSystemBarsAppearance(
- mSystemBarAppearance, LIGHT_BARS_MASK);
- }
-
@Override
public boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
if (mRootView == null) {
@@ -491,7 +472,6 @@
removeWindowInner(mRootView, false);
return true;
}
- clearSystemBarColor();
if (immediately
|| mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
removeWindowInner(mRootView, false);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index e2be153..1ce87ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -22,6 +22,7 @@
import static android.window.StartingWindowRemovalInfo.DEFER_MODE_ROTATION;
import android.annotation.CallSuper;
+import android.annotation.NonNull;
import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.content.Context;
@@ -306,7 +307,7 @@
@CallSuper
protected void removeImmediately() {
mRemoveExecutor.removeCallbacks(mScheduledRunnable);
- mRecordManager.onRecordRemoved(mTaskId);
+ mRecordManager.onRecordRemoved(this, mTaskId);
}
}
@@ -327,6 +328,11 @@
}
void addRecord(int taskId, StartingWindowRecord record) {
+ final StartingWindowRecord original = mStartingWindowRecords.get(taskId);
+ if (original != null) {
+ mTmpRemovalInfo.taskId = taskId;
+ original.removeIfPossible(mTmpRemovalInfo, true /* immediately */);
+ }
mStartingWindowRecords.put(taskId, record);
}
@@ -346,8 +352,11 @@
removeWindow(mTmpRemovalInfo, true/* immediately */);
}
- void onRecordRemoved(int taskId) {
- mStartingWindowRecords.remove(taskId);
+ void onRecordRemoved(@NonNull StartingWindowRecord record, int taskId) {
+ final StartingWindowRecord currentRecord = mStartingWindowRecords.get(taskId);
+ if (currentRecord == record) {
+ mStartingWindowRecords.remove(taskId);
+ }
}
StartingWindowRecord getRecord(int taskId) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/DisplayImeChangeListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/DisplayImeChangeListener.java
new file mode 100644
index 0000000..a94f802
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/DisplayImeChangeListener.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.sysui;
+
+import android.graphics.Rect;
+
+/**
+ * Callbacks for when the Display IME changes.
+ */
+public interface DisplayImeChangeListener {
+ /**
+ * Called when the ime bounds change.
+ */
+ default void onImeBoundsChanged(int displayId, Rect bounds) {}
+
+ /**
+ * Called when the IME visibility change.
+ */
+ default void onImeVisibilityChanged(int displayId, boolean isShowing) {}
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
index a7843e2..2f6edc2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@@ -30,21 +30,28 @@
import android.content.pm.ActivityInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.os.Bundle;
import android.util.ArrayMap;
+import android.view.InsetsSource;
+import android.view.InsetsState;
import android.view.SurfaceControlRegistry;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalThread;
import java.io.PrintWriter;
import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
import java.util.function.Supplier;
/**
@@ -57,6 +64,7 @@
private final ShellInit mShellInit;
private final ShellCommandHandler mShellCommandHandler;
private final ShellExecutor mMainExecutor;
+ private final DisplayInsetsController mDisplayInsetsController;
private final ShellInterfaceImpl mImpl = new ShellInterfaceImpl();
private final CopyOnWriteArrayList<ConfigurationChangeListener> mConfigChangeListeners =
@@ -65,6 +73,8 @@
new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<UserChangeListener> mUserChangeListeners =
new CopyOnWriteArrayList<>();
+ private final ConcurrentHashMap<DisplayImeChangeListener, Executor> mDisplayImeChangeListeners =
+ new ConcurrentHashMap<>();
private ArrayMap<String, Supplier<ExternalInterfaceBinder>> mExternalInterfaceSuppliers =
new ArrayMap<>();
@@ -73,20 +83,53 @@
private Configuration mLastConfiguration;
+ private OnInsetsChangedListener mInsetsChangeListener = new OnInsetsChangedListener() {
+ private InsetsState mInsetsState = new InsetsState();
+
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ if (mInsetsState == insetsState) {
+ return;
+ }
+
+ InsetsSource oldSource = mInsetsState.peekSource(InsetsSource.ID_IME);
+ boolean wasVisible = (oldSource != null && oldSource.isVisible());
+ Rect oldFrame = wasVisible ? oldSource.getFrame() : null;
+
+ InsetsSource newSource = insetsState.peekSource(InsetsSource.ID_IME);
+ boolean isVisible = (newSource != null && newSource.isVisible());
+ Rect newFrame = isVisible ? newSource.getFrame() : null;
+
+ if (wasVisible != isVisible) {
+ onImeVisibilityChanged(isVisible);
+ }
+
+ if (newFrame != null && !newFrame.equals(oldFrame)) {
+ onImeBoundsChanged(newFrame);
+ }
+
+ mInsetsState = insetsState;
+ }
+ };
+
public ShellController(Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
+ DisplayInsetsController displayInsetsController,
ShellExecutor mainExecutor) {
mContext = context;
mShellInit = shellInit;
mShellCommandHandler = shellCommandHandler;
+ mDisplayInsetsController = displayInsetsController;
mMainExecutor = mainExecutor;
shellInit.addInitCallback(this::onInit, this);
}
private void onInit() {
mShellCommandHandler.addDumpCallback(this::dump, this);
+ mDisplayInsetsController.addInsetsChangedListener(
+ mContext.getDisplayId(), mInsetsChangeListener);
}
/**
@@ -259,6 +302,25 @@
}
}
+ @VisibleForTesting
+ void onImeBoundsChanged(Rect bounds) {
+ ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Display Ime bounds changed");
+ mDisplayImeChangeListeners.forEach(
+ (DisplayImeChangeListener listener, Executor executor) ->
+ executor.execute(() -> listener.onImeBoundsChanged(
+ mContext.getDisplayId(), bounds)));
+ }
+
+ @VisibleForTesting
+ void onImeVisibilityChanged(boolean isShowing) {
+ ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Display Ime visibility changed: isShowing=%b",
+ isShowing);
+ mDisplayImeChangeListeners.forEach(
+ (DisplayImeChangeListener listener, Executor executor) ->
+ executor.execute(() -> listener.onImeVisibilityChanged(
+ mContext.getDisplayId(), isShowing)));
+ }
+
private void handleInit() {
SurfaceControlRegistry.createProcessInstance(mContext);
mShellInit.init();
@@ -329,6 +391,19 @@
}
@Override
+ public void addDisplayImeChangeListener(DisplayImeChangeListener listener,
+ Executor executor) {
+ ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Adding new DisplayImeChangeListener");
+ mDisplayImeChangeListeners.put(listener, executor);
+ }
+
+ @Override
+ public void removeDisplayImeChangeListener(DisplayImeChangeListener listener) {
+ ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Removing DisplayImeChangeListener");
+ mDisplayImeChangeListeners.remove(listener);
+ }
+
+ @Override
public boolean handleCommand(String[] args, PrintWriter pw) {
try {
boolean[] result = new boolean[1];
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
index bc5dd11..bd1c64a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
@@ -25,6 +25,7 @@
import java.io.PrintWriter;
import java.util.List;
+import java.util.concurrent.Executor;
/**
* General interface for notifying the Shell of common SysUI events like configuration or keyguard
@@ -65,6 +66,18 @@
default void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {}
/**
+ * Registers a DisplayImeChangeListener to monitor for changes on Ime
+ * position and visibility.
+ */
+ default void addDisplayImeChangeListener(DisplayImeChangeListener listener,
+ Executor executor) {}
+
+ /**
+ * Removes a registered DisplayImeChangeListener.
+ */
+ default void removeDisplayImeChangeListener(DisplayImeChangeListener listener) {}
+
+ /**
* Handles a shell command.
*/
default boolean handleCommand(final String[] args, PrintWriter pw) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java
index 61e11e8..89b0e25 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.transition;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS;
+
import android.annotation.NonNull;
import android.os.RemoteException;
import android.view.IRemoteAnimationFinishedCallback;
@@ -26,6 +28,8 @@
import android.view.WindowManager;
import android.window.IWindowContainerTransactionCallback;
+import com.android.internal.protolog.common.ProtoLog;
+
/**
* Utilities and interfaces for transition-like usage on top of the legacy app-transition and
* synctransaction tools.
@@ -87,9 +91,11 @@
@Override
public void onTransactionReady(int id, SurfaceControl.Transaction t)
throws RemoteException {
+ ProtoLog.v(WM_SHELL_TRANSITIONS,
+ "LegacyTransitions.onTransactionReady(): syncId=%d", id);
mSyncId = id;
mTransaction = t;
- checkApply();
+ checkApply(true /* log */);
}
}
@@ -103,20 +109,29 @@
mWallpapers = wallpapers;
mNonApps = nonApps;
mFinishCallback = finishedCallback;
- checkApply();
+ checkApply(false /* log */);
}
@Override
public void onAnimationCancelled() throws RemoteException {
mCancelled = true;
mApps = mWallpapers = mNonApps = null;
- checkApply();
+ checkApply(false /* log */);
}
}
- private void checkApply() throws RemoteException {
- if (mSyncId < 0 || (mFinishCallback == null && !mCancelled)) return;
+ private void checkApply(boolean log) throws RemoteException {
+ if (mSyncId < 0 || (mFinishCallback == null && !mCancelled)) {
+ if (log) {
+ ProtoLog.v(WM_SHELL_TRANSITIONS, "\tSkipping hasFinishedCb=%b canceled=%b",
+ mFinishCallback != null, mCancelled);
+ }
+ return;
+ }
+ if (log) {
+ ProtoLog.v(WM_SHELL_TRANSITIONS, "\tapply");
+ }
mLegacyTransition.onAnimationStart(mTransit, mApps, mWallpapers,
mNonApps, mFinishCallback, mTransaction);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index b8a0f67..ccd0b2d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -1409,6 +1409,8 @@
public void onTransitionReady(IBinder iBinder, TransitionInfo transitionInfo,
SurfaceControl.Transaction t, SurfaceControl.Transaction finishT)
throws RemoteException {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady(transaction=%d)",
+ t.getId());
mMainExecutor.execute(() -> Transitions.this.onTransitionReady(
iBinder, transitionInfo, t, finishT));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index b2eeea7..c59a1b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -19,9 +19,11 @@
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 android.view.WindowManager.TRANSIT_CHANGE;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
+import android.graphics.Rect;
import android.os.Handler;
import android.util.SparseArray;
import android.view.Choreographer;
@@ -186,7 +188,7 @@
final FluidResizeTaskPositioner taskPositioner =
new FluidResizeTaskPositioner(mTaskOrganizer, mTransitions, windowDecoration,
- mDisplayController, 0 /* disallowedAreaForEndBoundsHeight */);
+ mDisplayController);
final CaptionTouchEventListener touchEventListener =
new CaptionTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
@@ -286,8 +288,15 @@
mDragPointerId = e.getPointerId(0);
}
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
- mDragPositioningCallback.onDragPositioningEnd(
+ final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
+ DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(newTaskBounds,
+ mWindowDecorByTaskId.get(mTaskId).calculateValidDragArea());
+ if (newTaskBounds != taskInfo.configuration.windowConfiguration.getBounds()) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setBounds(taskInfo.token, newTaskBounds);
+ mTransitions.startTransition(TRANSIT_CHANGE, wct, null);
+ }
final boolean wasDragging = mIsDragging;
mIsDragging = false;
return wasDragging;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 91e9601..9a48922 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.windowdecor;
+import android.annotation.NonNull;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration;
import android.app.WindowConfiguration.WindowingMode;
@@ -87,6 +88,7 @@
}
@Override
+ @NonNull
Rect calculateValidDragArea() {
final int leftButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
R.dimen.caption_left_buttons_width);
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 500e6f4..918cefb 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
@@ -639,7 +639,7 @@
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
mDesktopTasksController.onDragPositioningEnd(taskInfo, position,
new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
- newTaskBounds);
+ newTaskBounds, decoration.calculateValidDragArea());
if (touchingButton && !mHasLongClicked) {
// We need the input event to not be consumed here to end the ripple
// effect on the touched button. We will reset drag state in the ensuing
@@ -866,6 +866,13 @@
return;
}
if (mTransitionDragActive) {
+ // Do not create an indicator at all if we're not past transition height.
+ DisplayLayout layout = mDisplayController
+ .getDisplayLayout(relevantDecor.mTaskInfo.displayId);
+ if (ev.getRawY() < 2 * layout.stableInsets().top
+ && mMoveToDesktopAnimator == null) {
+ return;
+ }
final DesktopModeVisualIndicator.IndicatorType indicatorType =
mDesktopTasksController.updateVisualIndicator(
relevantDecor.mTaskInfo,
@@ -1052,8 +1059,7 @@
&& taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED
&& taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
&& !taskInfo.configuration.windowConfiguration.isAlwaysOnTop()
- && mDisplayController.getDisplayContext(taskInfo.displayId)
- .getResources().getConfiguration().smallestScreenWidthDp >= 600;
+ && DesktopModeStatus.canEnterDesktopMode(mContext);
}
private void createWindowDecoration(
@@ -1081,18 +1087,16 @@
windowDecoration.createResizeVeil();
final DragPositioningCallback dragPositioningCallback;
- final int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
- R.dimen.desktop_mode_fullscreen_from_desktop_height);
if (!DesktopModeStatus.isVeiledResizeEnabled()) {
dragPositioningCallback = new FluidResizeTaskPositioner(
mTaskOrganizer, mTransitions, windowDecoration, mDisplayController,
- mDragStartListener, mTransactionFactory, transitionAreaHeight);
+ mDragStartListener, mTransactionFactory);
windowDecoration.setTaskDragResizer(
(FluidResizeTaskPositioner) dragPositioningCallback);
} else {
dragPositioningCallback = new VeiledResizeTaskPositioner(
mTaskOrganizer, windowDecoration, mDisplayController,
- mDragStartListener, mTransitions, transitionAreaHeight);
+ mDragStartListener, mTransitions);
windowDecoration.setTaskDragResizer(
(VeiledResizeTaskPositioner) dragPositioningCallback);
}
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 39803e2..4c9e171 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
@@ -22,6 +22,7 @@
import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
@@ -318,11 +319,12 @@
relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode());
relayoutParams.mCaptionWidthId = getCaptionWidthId(relayoutParams.mLayoutResId);
- if (captionLayoutId == R.layout.desktop_mode_app_controls_window_decor
- && TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) {
- // App is requesting to customize the caption bar. Allow input to fall through to the
- // windows below so that the app can respond to input events on their custom content.
- relayoutParams.mAllowCaptionInputFallthrough = true;
+ if (captionLayoutId == R.layout.desktop_mode_app_controls_window_decor) {
+ // If the app is requesting to customize the caption bar, allow input to fall through
+ // to the windows below so that the app can respond to input events on their custom
+ // content.
+ relayoutParams.mAllowCaptionInputFallthrough =
+ TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo);
// Report occluding elements as bounding rects to the insets system so that apps can
// draw in the empty space in the center:
// First, the "app chip" section of the caption bar (+ some extra margins).
@@ -498,6 +500,7 @@
* Determine valid drag area for this task based on elements in the app chip.
*/
@Override
+ @NonNull
Rect calculateValidDragArea() {
final int appTextWidth = ((DesktopModeAppControlsWindowDecorationViewHolder)
mWindowDecorViewHolder).getAppNameTextWidth();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index 5afbd54..82c399a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -131,7 +131,7 @@
t.setPosition(decoration.mTaskSurface, repositionTaskBounds.left, repositionTaskBounds.top);
}
- private static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
+ static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
PointF repositionStartPoint, float x, float y) {
final float deltaX = x - repositionStartPoint.x;
final float deltaY = y - repositionStartPoint.y;
@@ -140,49 +140,32 @@
}
/**
- * Calculates the new position of the top edge of the task and returns true if it is below the
- * disallowed area.
- *
- * @param disallowedAreaForEndBoundsHeight the height of the area that where the task positioner
- * should not finalize the bounds using WCT#setBounds
- * @param taskBoundsAtDragStart the bounds of the task on the first drag input event
- * @param repositionStartPoint initial input coordinate
- * @param y the y position of the motion event
- * @return true if the top of the task is below the disallowed area
+ * If task bounds are outside of provided drag area, snap the bounds to be just inside the
+ * drag area.
+ * @param repositionTaskBounds bounds determined by task positioner
+ * @param validDragArea the area that task must be positioned inside
+ * @return whether bounds were modified
*/
- static boolean isBelowDisallowedArea(int disallowedAreaForEndBoundsHeight,
- Rect taskBoundsAtDragStart, PointF repositionStartPoint, float y) {
- final float deltaY = y - repositionStartPoint.y;
- final float topPosition = taskBoundsAtDragStart.top + deltaY;
- return topPosition > disallowedAreaForEndBoundsHeight;
- }
-
- /**
- * Updates repositionTaskBounds to the final bounds of the task after the drag is finished. If
- * the bounds are outside of the valid drag area, the task is shifted back onto the edge of the
- * valid drag area.
- */
- static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
- PointF repositionStartPoint, float x, float y, Rect validDragArea) {
- updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint,
- x, y);
- snapTaskBoundsIfNecessary(repositionTaskBounds, validDragArea);
- }
-
- private static void snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea) {
+ public static boolean snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea) {
// If we were never supplied a valid drag area, do not restrict movement.
// Otherwise, we restrict deltas to keep task position inside the Rect.
- if (validDragArea.width() == 0) return;
+ if (validDragArea.width() == 0) return false;
+ boolean result = false;
if (repositionTaskBounds.left < validDragArea.left) {
repositionTaskBounds.offset(validDragArea.left - repositionTaskBounds.left, 0);
+ result = true;
} else if (repositionTaskBounds.left > validDragArea.right) {
repositionTaskBounds.offset(validDragArea.right - repositionTaskBounds.left, 0);
+ result = true;
}
if (repositionTaskBounds.top < validDragArea.top) {
repositionTaskBounds.offset(0, validDragArea.top - repositionTaskBounds.top);
+ result = true;
} else if (repositionTaskBounds.top > validDragArea.bottom) {
repositionTaskBounds.offset(0, validDragArea.bottom - repositionTaskBounds.top);
+ result = true;
}
+ return result;
}
private static float getMinWidth(DisplayController displayController,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 6bfc7cd..6f8b3d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -60,9 +60,6 @@
private final Rect mTaskBoundsAtDragStart = new Rect();
private final PointF mRepositionStartPoint = new PointF();
private final Rect mRepositionTaskBounds = new Rect();
- // If a task move (not resize) finishes with the positions y less than this value, do not
- // finalize the bounds there using WCT#setBounds
- private final int mDisallowedAreaForEndBoundsHeight;
private boolean mHasDragResized;
private boolean mIsResizingOrAnimatingResize;
private int mCtrlType;
@@ -70,11 +67,9 @@
@Surface.Rotation private int mRotation;
FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, Transitions transitions,
- WindowDecoration windowDecoration, DisplayController displayController,
- int disallowedAreaForEndBoundsHeight) {
+ WindowDecoration windowDecoration, DisplayController displayController) {
this(taskOrganizer, transitions, windowDecoration, displayController,
- dragStartListener -> {}, SurfaceControl.Transaction::new,
- disallowedAreaForEndBoundsHeight);
+ dragStartListener -> {}, SurfaceControl.Transaction::new);
}
FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
@@ -82,15 +77,13 @@
WindowDecoration windowDecoration,
DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Supplier<SurfaceControl.Transaction> supplier,
- int disallowedAreaForEndBoundsHeight) {
+ Supplier<SurfaceControl.Transaction> supplier) {
mTaskOrganizer = taskOrganizer;
mTransitions = transitions;
mWindowDecoration = windowDecoration;
mDisplayController = displayController;
mDragStartListener = dragStartListener;
mTransactionSupplier = supplier;
- mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
}
@Override
@@ -157,14 +150,10 @@
wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
}
mDragResizeEndTransition = mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
- } else if (mCtrlType == CTRL_TYPE_UNDEFINED
- && DragPositioningCallbackUtility.isBelowDisallowedArea(
- mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
- y)) {
+ } else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
- mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
- mWindowDecoration.calculateValidDragArea());
+ DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
+ mTaskBoundsAtDragStart, mRepositionStartPoint, x, y);
wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 5c69d55..c12a93e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -54,9 +54,6 @@
private final Rect mTaskBoundsAtDragStart = new Rect();
private final PointF mRepositionStartPoint = new PointF();
private final Rect mRepositionTaskBounds = new Rect();
- // If a task move (not resize) finishes with the positions y less than this value, do not
- // finalize the bounds there using WCT#setBounds
- private final int mDisallowedAreaForEndBoundsHeight;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
private int mCtrlType;
private boolean mIsResizingOrAnimatingResize;
@@ -66,25 +63,22 @@
DesktopModeWindowDecoration windowDecoration,
DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Transitions transitions,
- int disallowedAreaForEndBoundsHeight) {
+ Transitions transitions) {
this(taskOrganizer, windowDecoration, displayController, dragStartListener,
- SurfaceControl.Transaction::new, transitions, disallowedAreaForEndBoundsHeight);
+ SurfaceControl.Transaction::new, transitions);
}
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
DesktopModeWindowDecoration windowDecoration,
DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Supplier<SurfaceControl.Transaction> supplier, Transitions transitions,
- int disallowedAreaForEndBoundsHeight) {
+ Supplier<SurfaceControl.Transaction> supplier, Transitions transitions) {
mDesktopWindowDecoration = windowDecoration;
mTaskOrganizer = taskOrganizer;
mDisplayController = displayController;
mDragStartListener = dragStartListener;
mTransactionSupplier = supplier;
mTransitions = transitions;
- mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
}
@Override
@@ -151,13 +145,10 @@
// won't be called.
resetVeilIfVisible();
}
- } else if (DragPositioningCallbackUtility.isBelowDisallowedArea(
- mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
- y)) {
+ } else {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
- mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
- mDesktopWindowDecoration.calculateValidDragArea());
+ DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
+ mTaskBoundsAtDragStart, mRepositionStartPoint, x, y);
wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
index 5dd96ac..7a64a47 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
@@ -22,12 +22,12 @@
val TaskInfo.isTransparentCaptionBarAppearance: Boolean
get() {
- val appearance = taskDescription?.statusBarAppearance ?: 0
+ val appearance = taskDescription?.systemBarsAppearance ?: 0
return (appearance and APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND) != 0
}
val TaskInfo.isLightCaptionBarAppearance: Boolean
get() {
- val appearance = taskDescription?.statusBarAppearance ?: 0
+ val appearance = taskDescription?.systemBarsAppearance ?: 0
return (appearance and APPEARANCE_LIGHT_CAPTION_BARS) != 0
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
index 6dcae27..96bc4a1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
@@ -65,7 +65,7 @@
taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
Color.valueOf(taskDescription.statusBarColor).luminance() < 0.5
} else {
- taskDescription.statusBarAppearance and APPEARANCE_LIGHT_STATUS_BARS == 0
+ taskDescription.systemBarsAppearance and APPEARANCE_LIGHT_STATUS_BARS == 0
}
} ?: false
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
index 89ef91e..6171074 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
@@ -19,7 +19,6 @@
import android.app.Instrumentation
import android.tools.NavBar
import android.tools.Rotation
-import android.tools.AndroidLoggerSetupRule
import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
@@ -28,7 +27,6 @@
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
-import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -66,8 +64,4 @@
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
}
-
- companion object {
- @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
- }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
index 4336692..9e6a686 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
@@ -19,7 +19,6 @@
import android.app.Instrumentation
import android.tools.NavBar
import android.tools.Rotation
-import android.tools.AndroidLoggerSetupRule
import android.tools.flicker.rules.ChangeDisplayOrientationRule
import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
@@ -30,7 +29,6 @@
import org.junit.After
import org.junit.Assume
import org.junit.Before
-import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -77,8 +75,4 @@
secondaryApp.exit(wmHelper)
sendNotificationApp.exit(wmHelper)
}
-
- companion object {
- @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
- }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
index 8c7e63f..3f07be0 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
@@ -19,7 +19,6 @@
import android.app.Instrumentation
import android.tools.NavBar
import android.tools.Rotation
-import android.tools.AndroidLoggerSetupRule
import android.tools.flicker.rules.ChangeDisplayOrientationRule
import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
@@ -30,7 +29,6 @@
import org.junit.After
import org.junit.Assume
import org.junit.Before
-import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -88,8 +86,4 @@
secondaryApp.exit(wmHelper)
tapl.enableBlockTimeout(false)
}
-
- companion object {
- @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
- }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
index 2072831..5328013 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
@@ -19,7 +19,6 @@
import android.app.Instrumentation
import android.tools.NavBar
import android.tools.Rotation
-import android.tools.AndroidLoggerSetupRule
import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
@@ -29,7 +28,6 @@
import org.junit.After
import org.junit.Assume
import org.junit.Before
-import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -76,8 +74,4 @@
secondaryApp.exit(wmHelper)
tapl.enableBlockTimeout(false)
}
-
- companion object {
- @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
- }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
index 09e77cc..be4035d 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
@@ -19,7 +19,6 @@
import android.app.Instrumentation
import android.tools.NavBar
import android.tools.Rotation
-import android.tools.AndroidLoggerSetupRule
import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
@@ -28,7 +27,6 @@
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
-import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -72,8 +70,4 @@
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
}
-
- companion object {
- @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
- }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
index babdae1..9312c0a 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
@@ -20,7 +20,6 @@
import android.graphics.Point
import android.tools.NavBar
import android.tools.Rotation
-import android.tools.AndroidLoggerSetupRule
import android.tools.helpers.WindowUtils
import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
@@ -30,7 +29,6 @@
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
-import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -151,8 +149,4 @@
val LARGE_SCREEN_DP_THRESHOLD = 600
return sizeDp.x >= LARGE_SCREEN_DP_THRESHOLD && sizeDp.y >= LARGE_SCREEN_DP_THRESHOLD
}
-
- companion object {
- @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
- }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
index 3e85479..de26982 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
@@ -19,7 +19,6 @@
import android.app.Instrumentation
import android.tools.NavBar
import android.tools.Rotation
-import android.tools.AndroidLoggerSetupRule
import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
@@ -28,7 +27,6 @@
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
-import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -69,8 +67,4 @@
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
}
-
- companion object {
- @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
- }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
index 655ae4e..873b019 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
@@ -19,7 +19,6 @@
import android.app.Instrumentation
import android.tools.NavBar
import android.tools.Rotation
-import android.tools.AndroidLoggerSetupRule
import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
@@ -28,7 +27,6 @@
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
-import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -68,8 +66,4 @@
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
}
-
- companion object {
- @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
- }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
index 2208258..15934d0 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
@@ -19,7 +19,6 @@
import android.app.Instrumentation
import android.tools.NavBar
import android.tools.Rotation
-import android.tools.AndroidLoggerSetupRule
import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
@@ -28,7 +27,6 @@
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
-import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -70,8 +68,4 @@
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
}
-
- companion object {
- @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
- }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
index 2ac63c2..79e69ae 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
@@ -19,7 +19,6 @@
import android.app.Instrumentation
import android.tools.NavBar
import android.tools.Rotation
-import android.tools.AndroidLoggerSetupRule
import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
@@ -28,7 +27,6 @@
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
-import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -71,8 +69,4 @@
thirdApp.exit(wmHelper)
fourthApp.exit(wmHelper)
}
-
- companion object {
- @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
- }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
index 35b122d..0f932d4 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
@@ -19,7 +19,6 @@
import android.app.Instrumentation
import android.tools.NavBar
import android.tools.Rotation
-import android.tools.AndroidLoggerSetupRule
import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
@@ -28,7 +27,6 @@
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
-import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -68,8 +66,4 @@
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
}
-
- companion object {
- @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
- }
}
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 fa0aba5..48e396a 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
@@ -49,6 +49,8 @@
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.bubbles.BubbleData.TimeSource;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
+import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
import com.google.common.collect.ImmutableList;
@@ -1207,6 +1209,19 @@
assertOverflowChangedTo(ImmutableList.of());
}
+ @Test
+ public void test_getInitialStateForBubbleBar_includesInitialBubblesAndPosition() {
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ sendUpdatedEntryAtTime(mEntryA2, 2000);
+ mPositioner.setBubbleBarLocation(BubbleBarLocation.LEFT);
+
+ BubbleBarUpdate update = mBubbleData.getInitialStateForBubbleBar();
+ assertThat(update.currentBubbleList).hasSize(2);
+ assertThat(update.currentBubbleList.get(0).getKey()).isEqualTo(mEntryA2.getKey());
+ assertThat(update.currentBubbleList.get(1).getKey()).isEqualTo(mEntryA1.getKey());
+ assertThat(update.bubbleBarLocation).isEqualTo(BubbleBarLocation.LEFT);
+ }
+
private void verifyUpdateReceived() {
verify(mListener).applyUpdate(mUpdateCaptor.capture());
reset(mListener);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index ae39fbc..4a4c5e8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -37,6 +37,7 @@
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView
import com.android.wm.shell.bubbles.properties.BubbleProperties
import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayInsetsController
import com.android.wm.shell.common.FloatingContentCoordinator
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
@@ -94,7 +95,8 @@
val windowManager = context.getSystemService(WindowManager::class.java)
val shellInit = ShellInit(mainExecutor)
val shellCommandHandler = ShellCommandHandler()
- val shellController = ShellController(context, shellInit, shellCommandHandler, mainExecutor)
+ val shellController = ShellController(context, shellInit, shellCommandHandler,
+ mock<DisplayInsetsController>(), mainExecutor)
bubblePositioner = BubblePositioner(context, windowManager)
val bubbleData =
BubbleData(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleBarLocationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleBarLocationTest.kt
new file mode 100644
index 0000000..27e0b19
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleBarLocationTest.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.common.bubbles
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.bubbles.BubbleBarLocation.DEFAULT
+import com.android.wm.shell.common.bubbles.BubbleBarLocation.LEFT
+import com.android.wm.shell.common.bubbles.BubbleBarLocation.RIGHT
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class BubbleBarLocationTest : ShellTestCase() {
+
+ @Test
+ fun isOnLeft_rtlEnabled_defaultsToLeft() {
+ assertThat(DEFAULT.isOnLeft(isRtl = true)).isTrue()
+ }
+
+ @Test
+ fun isOnLeft_rtlDisabled_defaultsToRight() {
+ assertThat(DEFAULT.isOnLeft(isRtl = false)).isFalse()
+ }
+
+ @Test
+ fun isOnLeft_left_trueForAllLanguageDirections() {
+ assertThat(LEFT.isOnLeft(isRtl = false)).isTrue()
+ assertThat(LEFT.isOnLeft(isRtl = true)).isTrue()
+ }
+
+ @Test
+ fun isOnLeft_right_falseForAllLanguageDirections() {
+ assertThat(RIGHT.isOnLeft(isRtl = false)).isFalse()
+ assertThat(RIGHT.isOnLeft(isRtl = true)).isFalse()
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
new file mode 100644
index 0000000..65117f7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.desktopmode
+
+import android.app.ActivityManager
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.view.WindowManager.TRANSIT_CLOSE
+import android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS
+import android.view.WindowManager.TRANSIT_NONE
+import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_SLEEP
+import android.view.WindowManager.TRANSIT_TO_BACK
+import android.view.WindowManager.TRANSIT_TO_FRONT
+import android.view.WindowManager.TRANSIT_WAKE
+import android.window.IWindowContainerToken
+import android.window.TransitionInfo
+import android.window.TransitionInfo.Change
+import android.window.WindowContainerToken
+import androidx.test.filters.SmallTest
+import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.TransitionInfoBuilder
+import com.android.wm.shell.transition.Transitions
+import com.google.common.truth.Truth.assertThat
+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.Mockito
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.never
+import org.mockito.kotlin.same
+import org.mockito.kotlin.times
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopModeLoggerTransitionObserverTest {
+
+ @JvmField
+ @Rule
+ val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
+ .mockStatic(DesktopModeEventLogger::class.java)
+ .mockStatic(DesktopModeStatus::class.java).build()!!
+
+ @Mock
+ lateinit var testExecutor: ShellExecutor
+ @Mock
+ private lateinit var mockShellInit: ShellInit
+ @Mock
+ private lateinit var transitions: Transitions
+
+ private lateinit var transitionObserver: DesktopModeLoggerTransitionObserver
+ private lateinit var shellInit: ShellInit
+ private lateinit var desktopModeEventLogger: DesktopModeEventLogger
+
+ @Before
+ fun setup() {
+ Mockito.`when`(DesktopModeStatus.isEnabled()).thenReturn(true)
+ shellInit = Mockito.spy(ShellInit(testExecutor))
+ desktopModeEventLogger = mock(DesktopModeEventLogger::class.java)
+
+ transitionObserver = DesktopModeLoggerTransitionObserver(
+ mockShellInit, transitions, desktopModeEventLogger)
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ val initRunnableCaptor = ArgumentCaptor.forClass(
+ Runnable::class.java)
+ verify(mockShellInit).addInitCallback(initRunnableCaptor.capture(),
+ same(transitionObserver))
+ initRunnableCaptor.value.run()
+ } else {
+ transitionObserver.onInit()
+ }
+ }
+
+ @Test
+ fun testRegistersObserverAtInit() {
+ verify(transitions)
+ .registerObserver(same(
+ transitionObserver))
+ }
+
+ @Test
+ fun taskCreated_notFreeformWindow_doesNotLogSessionEnterOrTaskAdded() {
+ val change = createChange(TRANSIT_OPEN, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, never()).logSessionEnter(any(), any())
+ verify(desktopModeEventLogger, never()).logTaskAdded(any(), any())
+ }
+
+ @Test
+ fun taskCreated_FreeformWindowOpen_logSessionEnterAndTaskAdded() {
+ val change = createChange(TRANSIT_OPEN, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+ val sessionId = transitionObserver.getLoggerSessionId()
+
+ assertThat(sessionId).isNotNull()
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+ eq(EnterReason.APP_FREEFORM_INTENT))
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+ }
+
+ @Test
+ fun taskChanged_taskMovedToDesktopByDrag_logSessionEnterAndTaskAdded() {
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ // task change is finalised when drag ends
+ val transitionInfo = TransitionInfoBuilder(
+ Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+ val sessionId = transitionObserver.getLoggerSessionId()
+
+ assertThat(sessionId).isNotNull()
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+ eq(EnterReason.APP_HANDLE_DRAG))
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+ }
+
+ @Test
+ fun taskChanged_taskMovedToDesktopByButtonTap_logSessionEnterAndTaskAdded() {
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(Transitions.TRANSIT_MOVE_TO_DESKTOP, 0)
+ .addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+ val sessionId = transitionObserver.getLoggerSessionId()
+
+ assertThat(sessionId).isNotNull()
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+ eq(EnterReason.APP_HANDLE_MENU_BUTTON))
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+ }
+
+ @Test
+ fun taskChanged_existingFreeformTaskMadeVisible_logSessionEnterAndTaskAdded() {
+ val taskInfo = createTaskInfo(1, WINDOWING_MODE_FREEFORM)
+ taskInfo.isVisibleRequested = true
+ val change = createChange(TRANSIT_CHANGE, taskInfo)
+ val transitionInfo = TransitionInfoBuilder(Transitions.TRANSIT_MOVE_TO_DESKTOP, 0)
+ .addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+ val sessionId = transitionObserver.getLoggerSessionId()
+
+ assertThat(sessionId).isNotNull()
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+ eq(EnterReason.APP_HANDLE_MENU_BUTTON))
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+ }
+
+ @Test
+ fun taskToFront_screenWake_logSessionStartedAndTaskAdded() {
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_WAKE, 0)
+ .addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+ val sessionId = transitionObserver.getLoggerSessionId()
+
+ assertThat(sessionId).isNotNull()
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+ eq(EnterReason.SCREEN_ON))
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+ }
+
+ @Test
+ fun freeformTaskVisible_screenTurnOff_logSessionExitAndTaskRemoved_sessionIdNull() {
+ val sessionId = 1
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_SLEEP).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+ verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+ eq(ExitReason.SCREEN_OFF))
+ assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ }
+
+ @Test
+ fun freeformTaskVisible_exitDesktopUsingDrag_logSessionExitAndTaskRemoved_sessionIdNull() {
+ val sessionId = 1
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ // window mode changing from FREEFORM to FULLSCREEN
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+ val transitionInfo = TransitionInfoBuilder(Transitions.TRANSIT_EXIT_DESKTOP_MODE)
+ .addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+ verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+ eq(ExitReason.DRAG_TO_EXIT))
+ assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ }
+
+ @Test
+ fun freeformTaskVisible_exitDesktopBySwipeUp_logSessionExitAndTaskRemoved_sessionIdNull() {
+ val sessionId = 1
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ // recents transition
+ val change = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
+ .addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+ verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+ eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
+ assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ }
+
+ @Test
+ fun freeformTaskVisible_taskFinished_logSessionExitAndTaskRemoved_sessionIdNull() {
+ val sessionId = 1
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ // task closing
+ val change = createChange(TRANSIT_CLOSE, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE).addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+ verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+ eq(ExitReason.TASK_FINISHED))
+ assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ }
+
+ @Test
+ fun sessionExitByRecents_cancelledAnimation_sessionRestored() {
+ val sessionId = 1
+ // add a freeform task to an existing session
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ // recents transition sent freeform window to back
+ val change = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val transitionInfo1 =
+ TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS).addChange(change)
+ .build()
+ callOnTransitionReady(transitionInfo1)
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+ verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+ eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
+ assertThat(transitionObserver.getLoggerSessionId()).isNull()
+
+ val transitionInfo2 = TransitionInfoBuilder(TRANSIT_NONE).build()
+ callOnTransitionReady(transitionInfo2)
+
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(any(), any())
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(any(), any())
+ }
+
+ @Test
+ fun sessionAlreadyStarted_newFreeformTaskAdded_logsTaskAdded() {
+ val sessionId = 1
+ // add an existing freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ // new freeform task added
+ val change = createChange(TRANSIT_OPEN, createTaskInfo(2, WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+ verify(desktopModeEventLogger, never()).logSessionEnter(any(), any())
+ }
+
+ @Test
+ fun sessionAlreadyStarted_freeformTaskRemoved_logsTaskRemoved() {
+ val sessionId = 1
+ // add two existing freeform tasks
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(2, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ // new freeform task added
+ val change = createChange(TRANSIT_CLOSE, createTaskInfo(2, WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+ verify(desktopModeEventLogger, never()).logSessionExit(any(), any())
+ }
+
+ /**
+ * Simulate calling the onTransitionReady() method
+ */
+ private fun callOnTransitionReady(transitionInfo: TransitionInfo) {
+ val transition = mock(IBinder::class.java)
+ val startT = mock(
+ SurfaceControl.Transaction::class.java)
+ val finishT = mock(
+ SurfaceControl.Transaction::class.java)
+
+ transitionObserver.onTransitionReady(transition, transitionInfo, startT, finishT)
+ }
+
+ companion object {
+ fun createTaskInfo(taskId: Int, windowMode: Int): ActivityManager.RunningTaskInfo {
+ val taskInfo = ActivityManager.RunningTaskInfo()
+ taskInfo.taskId = taskId
+ taskInfo.configuration.windowConfiguration.windowingMode = windowMode
+
+ return taskInfo
+ }
+
+ fun createChange(mode: Int, taskInfo: ActivityManager.RunningTaskInfo): Change {
+ val change = Change(
+ WindowContainerToken(mock(
+ IWindowContainerToken::class.java)),
+ mock(SurfaceControl::class.java))
+ change.mode = mode
+ change.taskInfo = taskInfo
+ return change
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
index 9703dce..bd39aa6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
@@ -68,17 +68,17 @@
)
var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
WINDOWING_MODE_FULLSCREEN, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
+ assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, 2 * STABLE_INSETS.top))
testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
WINDOWING_MODE_FREEFORM, CAPTION_HEIGHT)
assertThat(testRegion.bounds).isEqualTo(Rect(
DISPLAY_BOUNDS.width() / 2 - fromFreeformWidth / 2,
-50,
DISPLAY_BOUNDS.width() / 2 + fromFreeformWidth / 2,
- 2 * STABLE_INSETS.top))
+ transitionHeight))
testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
WINDOWING_MODE_MULTI_WINDOW, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
+ assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, 2 * STABLE_INSETS.top))
}
@Test
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 35c803b..5df9dd3 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
@@ -36,6 +36,7 @@
import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.ExtendedMockito.never
import com.android.dx.mockito.inline.extended.StaticMockitoSession
@@ -51,6 +52,7 @@
import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.common.split.SplitScreenConstants
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
@@ -85,7 +87,9 @@
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.verify
+import org.mockito.kotlin.times
import org.mockito.Mockito.`when` as whenever
+import org.mockito.quality.Strictness
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -110,6 +114,7 @@
@Mock lateinit var recentsTransitionHandler: RecentsTransitionHandler
@Mock lateinit var dragAndDropController: DragAndDropController
@Mock lateinit var multiInstanceHelper: MultiInstanceHelper
+ @Mock lateinit var desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var controller: DesktopTasksController
@@ -123,7 +128,8 @@
@Before
fun setUp() {
- mockitoSession = mockitoSession().mockStatic(DesktopModeStatus::class.java).startMocking()
+ mockitoSession = mockitoSession().strictness(Strictness.LENIENT)
+ .spyStatic(DesktopModeStatus::class.java).startMocking()
whenever(DesktopModeStatus.isEnabled()).thenReturn(true)
shellInit = Mockito.spy(ShellInit(testExecutor))
@@ -159,6 +165,7 @@
mToggleResizeDesktopTaskTransitionHandler,
dragToDesktopTransitionHandler,
desktopModeTaskRepository,
+ desktopModeLoggerTransitionObserver,
launchAdjacentController,
recentsTransitionHandler,
multiInstanceHelper,
@@ -332,6 +339,45 @@
}
@Test
+ fun moveToDesktop_deviceNotSupported_doesNothing() {
+ val task = setUpFullscreenTask()
+
+ // Simulate non compatible device
+ doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+
+ controller.moveToDesktop(task)
+ verifyWCTNotExecuted()
+ }
+
+ @Test
+ fun moveToDesktop_deviceNotSupported_deviceRestrictionsOverridden_taskIsMovedToDesktop() {
+ val task = setUpFullscreenTask()
+
+ // Simulate non compatible device
+ doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+
+ // Simulate enforce device restrictions system property overridden to false
+ whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(false)
+
+ controller.moveToDesktop(task)
+
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ fun moveToDesktop_deviceSupported_taskIsMovedToDesktop() {
+ val task = setUpFullscreenTask()
+
+ controller.moveToDesktop(task)
+
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
fun moveToDesktop_otherFreeformTasksBroughtToFront() {
val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
@@ -798,6 +844,22 @@
.isEqualTo(WINDOWING_MODE_FULLSCREEN)
}
+ fun enterSplit_freeformTaskIsMovedToSplit() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+
+ controller.enterSplit(DEFAULT_DISPLAY, false)
+
+ verify(splitScreenController).requestEnterSplitSelect(task2, any(),
+ SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT,
+ task2.configuration.windowConfiguration.bounds)
+ }
+
private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
val task = createFreeformTask(displayId)
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
@@ -816,6 +878,8 @@
private fun setUpFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
val task = createFullscreenTask(displayId)
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
runningTasks.add(task)
return task
@@ -823,6 +887,8 @@
private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
val task = createSplitScreenTask(displayId)
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
whenever(splitScreenController.isTaskInSplitScreen(task.taskId)).thenReturn(true)
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
runningTasks.add(task)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 3384509..d38fc6c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -129,7 +129,7 @@
}).when(mMockExecutor).execute(any());
mShellInit = spy(new ShellInit(mMockExecutor));
mShellController = spy(new ShellController(mContext, mShellInit, mMockShellCommandHandler,
- mMockExecutor));
+ mMockDisplayInsetsController, mMockExecutor));
mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler,
mShellController, mMockDisplayController, mMockPipAnimationController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 10e9e11..41a4e8d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -58,6 +58,7 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
@@ -96,6 +97,8 @@
private DesktopModeTaskRepository mDesktopModeTaskRepository;
@Mock
private ActivityTaskManager mActivityTaskManager;
+ @Mock
+ private DisplayInsetsController mDisplayInsetsController;
private ShellTaskOrganizer mShellTaskOrganizer;
private RecentTasksController mRecentTasksController;
@@ -110,7 +113,7 @@
when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
mShellInit = spy(new ShellInit(mMainExecutor));
mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
- mMainExecutor));
+ mDisplayInsetsController, mMainExecutor));
mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit,
mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
Optional.of(mDesktopModeTaskRepository), mMainExecutor);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 315d97e..3c387f0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -123,7 +123,7 @@
assumeTrue(ActivityTaskManager.supportsSplitScreenMultiWindow(mContext));
MockitoAnnotations.initMocks(this);
mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
- mMainExecutor));
+ mDisplayInsetsController, mMainExecutor));
mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit,
mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
mRootTDAOrganizer, mDisplayController, mDisplayImeController,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
index 012c4081..ff76a2f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
@@ -40,6 +40,7 @@
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -65,6 +66,7 @@
private @Mock Context mContext;
private @Mock DisplayManager mDisplayManager;
+ private @Mock DisplayInsetsController mDisplayInsetsController;
private @Mock ShellCommandHandler mShellCommandHandler;
private @Mock ShellTaskOrganizer mTaskOrganizer;
private @Mock ShellExecutor mMainExecutor;
@@ -83,7 +85,7 @@
doReturn(super.mContext.getResources()).when(mContext).getResources();
mShellInit = spy(new ShellInit(mMainExecutor));
mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
- mMainExecutor));
+ mDisplayInsetsController, mMainExecutor));
mController = new StartingWindowController(mContext, mShellInit, mShellController,
mTaskOrganizer, mMainExecutor, mTypeAlgorithm, mIconProvider, mTransactionPool);
mShellInit.init();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
index 7c520c3..6292018 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -35,8 +36,8 @@
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ExternalInterfaceBinder;
-import com.android.wm.shell.common.ShellExecutor;
import org.junit.After;
import org.junit.Before;
@@ -63,12 +64,15 @@
private ShellCommandHandler mShellCommandHandler;
@Mock
private Context mTestUserContext;
+ @Mock
+ private DisplayInsetsController mDisplayInsetsController;
private TestShellExecutor mExecutor;
private ShellController mController;
private TestConfigurationChangeListener mConfigChangeListener;
private TestKeyguardChangeListener mKeyguardChangeListener;
private TestUserChangeListener mUserChangeListener;
+ private TestDisplayImeChangeListener mDisplayImeChangeListener;
@Before
@@ -77,8 +81,10 @@
mKeyguardChangeListener = new TestKeyguardChangeListener();
mConfigChangeListener = new TestConfigurationChangeListener();
mUserChangeListener = new TestUserChangeListener();
+ mDisplayImeChangeListener = new TestDisplayImeChangeListener();
mExecutor = new TestShellExecutor();
- mController = new ShellController(mContext, mShellInit, mShellCommandHandler, mExecutor);
+ mController = new ShellController(mContext, mShellInit, mShellCommandHandler,
+ mDisplayInsetsController, mExecutor);
mController.onConfigurationChanged(getConfigurationCopy());
}
@@ -130,6 +136,45 @@
}
@Test
+ public void testAddDisplayImeChangeListener_ensureCallback() {
+ mController.asShell().addDisplayImeChangeListener(
+ mDisplayImeChangeListener, mExecutor);
+
+ final Rect bounds = new Rect(10, 20, 30, 40);
+ mController.onImeBoundsChanged(bounds);
+ mController.onImeVisibilityChanged(true);
+ mExecutor.flushAll();
+
+ assertTrue(mDisplayImeChangeListener.boundsChanged == 1);
+ assertTrue(bounds.equals(mDisplayImeChangeListener.lastBounds));
+ assertTrue(mDisplayImeChangeListener.visibilityChanged == 1);
+ assertTrue(mDisplayImeChangeListener.lastVisibility);
+ }
+
+ @Test
+ public void testDoubleAddDisplayImeChangeListener_ensureSingleCallback() {
+ mController.asShell().addDisplayImeChangeListener(
+ mDisplayImeChangeListener, mExecutor);
+ mController.asShell().addDisplayImeChangeListener(
+ mDisplayImeChangeListener, mExecutor);
+
+ mController.onImeVisibilityChanged(true);
+ mExecutor.flushAll();
+ assertTrue(mDisplayImeChangeListener.visibilityChanged == 1);
+ }
+
+ @Test
+ public void testAddRemoveDisplayImeChangeListener_ensureNoCallback() {
+ mController.asShell().addDisplayImeChangeListener(
+ mDisplayImeChangeListener, mExecutor);
+ mController.asShell().removeDisplayImeChangeListener(mDisplayImeChangeListener);
+
+ mController.onImeVisibilityChanged(true);
+ mExecutor.flushAll();
+ assertTrue(mDisplayImeChangeListener.visibilityChanged == 0);
+ }
+
+ @Test
public void testAddUserChangeListener_ensureCallback() {
mController.addUserChangeListener(mUserChangeListener);
@@ -457,4 +502,23 @@
lastUserProfiles = profiles;
}
}
+
+ private static class TestDisplayImeChangeListener implements DisplayImeChangeListener {
+ public int boundsChanged = 0;
+ public Rect lastBounds;
+ public int visibilityChanged = 0;
+ public boolean lastVisibility = false;
+
+ @Override
+ public void onImeBoundsChanged(int displayId, Rect bounds) {
+ boundsChanged++;
+ lastBounds = bounds;
+ }
+
+ @Override
+ public void onImeVisibilityChanged(int displayId, boolean isShowing) {
+ visibilityChanged++;
+ lastVisibility = isShowing;
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 9bb5482..6940739 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -27,6 +27,8 @@
import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.os.Handler
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.util.SparseArray
@@ -42,6 +44,10 @@
import android.view.WindowInsets.Type.navigationBars
import android.view.WindowInsets.Type.statusBars
import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.window.flags.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
@@ -51,6 +57,7 @@
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopModeStatus
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.sysui.KeyguardChangeListener
import com.android.wm.shell.sysui.ShellCommandHandler
@@ -59,6 +66,7 @@
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
@@ -71,6 +79,7 @@
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
import java.util.Optional
import java.util.function.Supplier
import org.mockito.Mockito
@@ -82,6 +91,10 @@
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
+ @JvmField
+ @Rule
+ val setFlagsRule = SetFlagsRule()
+
@Mock private lateinit var mockDesktopModeWindowDecorFactory:
DesktopModeWindowDecoration.Factory
@Mock private lateinit var mockMainHandler: Handler
@@ -351,6 +364,76 @@
inOrder.verify(windowDecorByTaskIdSpy).remove(task.taskId)
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun testWindowDecor_desktopModeUnsupportedOnDevice_decorNotCreated() {
+ val mockitoSession: StaticMockitoSession = mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .spyStatic(DesktopModeStatus::class.java)
+ .startMocking()
+ try {
+ // Simulate default enforce device restrictions system property
+ whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
+
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
+ // Simulate device that doesn't support desktop mode
+ doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+
+ onTaskOpening(task)
+ verify(mockDesktopModeWindowDecorFactory, never())
+ .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+ } finally {
+ mockitoSession.finishMocking()
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun testWindowDecor_desktopModeUnsupportedOnDevice_deviceRestrictionsOverridden_decorCreated() {
+ val mockitoSession: StaticMockitoSession = mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .spyStatic(DesktopModeStatus::class.java)
+ .startMocking()
+ try {
+ // Simulate enforce device restrictions system property overridden to false
+ whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(false)
+ // Simulate device that doesn't support desktop mode
+ doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
+ setUpMockDecorationsForTasks(task)
+
+ onTaskOpening(task)
+ verify(mockDesktopModeWindowDecorFactory)
+ .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+ } finally {
+ mockitoSession.finishMocking()
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun testWindowDecor_deviceSupportsDesktopMode_decorCreated() {
+ val mockitoSession: StaticMockitoSession = mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .spyStatic(DesktopModeStatus::class.java)
+ .startMocking()
+ try {
+ // Simulate default enforce device restrictions system property
+ whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
+
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ setUpMockDecorationsForTasks(task)
+
+ onTaskOpening(task)
+ verify(mockDesktopModeWindowDecorFactory)
+ .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+ } finally {
+ mockitoSession.finishMocking()
+ }
+ }
+
private fun onTaskOpening(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) {
desktopModeWindowDecorViewModel.onTaskOpening(
task,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 9e62bd2..f9b5882 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -173,10 +173,10 @@
}
@Test
- public void updateRelayoutParams_freeformAndTransparent_allowsInputFallthrough() {
+ public void updateRelayoutParams_freeformAndTransparentAppearance_allowsInputFallthrough() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
- taskInfo.taskDescription.setStatusBarAppearance(
+ taskInfo.taskDescription.setSystemBarsAppearance(
APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND);
final RelayoutParams relayoutParams = new RelayoutParams();
@@ -191,10 +191,10 @@
}
@Test
- public void updateRelayoutParams_freeformButOpaque_disallowsInputFallthrough() {
+ public void updateRelayoutParams_freeformButOpaqueAppearance_disallowsInputFallthrough() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
- taskInfo.taskDescription.setStatusBarAppearance(0);
+ taskInfo.taskDescription.setSystemBarsAppearance(0);
final RelayoutParams relayoutParams = new RelayoutParams();
DesktopModeWindowDecoration.updateRelayoutParams(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
index e60be71..e6fabcf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
@@ -189,8 +189,9 @@
DISPLAY_BOUNDS.right - 100,
DISPLAY_BOUNDS.bottom - 100)
- DragPositioningCallbackUtility.onDragEnd(repositionTaskBounds, STARTING_BOUNDS,
- startingPoint, startingPoint.x - 1000, (DISPLAY_BOUNDS.bottom + 1000).toFloat(),
+ DragPositioningCallbackUtility.updateTaskBounds(repositionTaskBounds, STARTING_BOUNDS,
+ startingPoint, startingPoint.x - 1000, (DISPLAY_BOUNDS.bottom + 1000).toFloat())
+ DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(repositionTaskBounds,
validDragArea)
assertThat(repositionTaskBounds.left).isEqualTo(validDragArea.left)
assertThat(repositionTaskBounds.top).isEqualTo(validDragArea.bottom)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index de6903d..ce7b633 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -125,8 +125,7 @@
mockWindowDecoration,
mockDisplayController,
mockDragStartListener,
- mockTransactionFactory,
- DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT
+ mockTransactionFactory
)
}
@@ -576,31 +575,6 @@
})
}
- @Test
- fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_UNDEFINED, // drag
- STARTING_BOUNDS.right.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- val newX = STARTING_BOUNDS.right.toFloat() + 5
- val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
-
- taskPositioner.onDragPositioningEnd(newX, newY)
-
- verify(mockTransitions, never()).startTransition(
- eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
- }}, eq(taskPositioner))
- }
-
private fun WindowContainerTransaction.Change.ofBounds(bounds: Rect): Boolean {
return ((windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) &&
bounds == configuration.windowConfiguration.bounds
@@ -656,70 +630,6 @@
}
@Test
- fun testDragResize_drag_taskPositionedInStableBounds() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_UNDEFINED, // drag
- STARTING_BOUNDS.left.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- val newX = STARTING_BOUNDS.left.toFloat()
- val newY = STABLE_BOUNDS_LANDSCAPE.top.toFloat() - 5
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
- verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
-
- taskPositioner.onDragPositioningEnd(
- newX,
- newY
- )
- // Verify task's top bound is set to stable bounds top since dragged outside stable bounds
- // but not in disallowed end bounds area.
- verify(mockTransitions).startTransition(
- eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
- change.configuration.windowConfiguration.bounds.top ==
- STABLE_BOUNDS_LANDSCAPE.top
- }}, eq(taskPositioner))
- }
-
- @Test
- fun testDragResize_drag_taskPositionedInValidDragArea() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_UNDEFINED, // drag
- STARTING_BOUNDS.left.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- val newX = VALID_DRAG_AREA.left - 500f
- val newY = VALID_DRAG_AREA.bottom + 500f
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
- verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
-
- taskPositioner.onDragPositioningEnd(
- newX,
- newY
- )
- verify(mockTransitions).startTransition(
- eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
- change.configuration.windowConfiguration.bounds.top ==
- VALID_DRAG_AREA.bottom &&
- change.configuration.windowConfiguration.bounds.left ==
- VALID_DRAG_AREA.left
- }}, eq(taskPositioner))
- }
-
- @Test
fun testDragResize_drag_updatesStableBoundsOnRotate() {
// Test landscape stable bounds
performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 86253f3..7f6e538 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -138,8 +138,7 @@
mockDisplayController,
mockDragStartListener,
mockTransactionFactory,
- mockTransitions,
- DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT
+ mockTransitions
)
}
@@ -355,68 +354,6 @@
}
@Test
- fun testDragResize_drag_taskPositionedInStableBounds() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_UNDEFINED, // drag
- STARTING_BOUNDS.left.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- val newX = STARTING_BOUNDS.left.toFloat()
- val newY = STABLE_BOUNDS_LANDSCAPE.top.toFloat() - 5
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
- verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
-
- taskPositioner.onDragPositioningEnd(
- newX,
- newY
- )
- // Verify task's top bound is set to stable bounds top since dragged outside stable bounds
- // but not in disallowed end bounds area.
- verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
- change.configuration.windowConfiguration.bounds.top ==
- STABLE_BOUNDS_LANDSCAPE.top }},
- eq(taskPositioner))
- }
-
- @Test
- fun testDragResize_drag_taskPositionedInValidDragArea() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_UNDEFINED, // drag
- STARTING_BOUNDS.left.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- val newX = VALID_DRAG_AREA.left - 500f
- val newY = VALID_DRAG_AREA.bottom + 500f
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
- verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
-
- taskPositioner.onDragPositioningEnd(
- newX,
- newY
- )
- verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
- change.configuration.windowConfiguration.bounds.top ==
- VALID_DRAG_AREA.bottom &&
- change.configuration.windowConfiguration.bounds.left ==
- VALID_DRAG_AREA.left }},
- eq(taskPositioner))
- }
-
- @Test
fun testDragResize_drag_updatesStableBoundsOnRotate() {
// Test landscape stable bounds
performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 54f94f5..4486f55 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -428,7 +428,6 @@
"jni/MovieImpl.cpp",
"jni/pdf/PdfDocument.cpp",
"jni/pdf/PdfEditor.cpp",
- "jni/pdf/PdfRenderer.cpp",
"jni/pdf/PdfUtils.cpp",
],
shared_libs: [
diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp
index 32bc122..af7a496 100644
--- a/libs/hwui/DeviceInfo.cpp
+++ b/libs/hwui/DeviceInfo.cpp
@@ -108,6 +108,10 @@
get()->mSupportFp16ForHdr = supportFp16ForHdr;
}
+void DeviceInfo::setSupportRgba10101010ForHdr(bool supportRgba10101010ForHdr) {
+ get()->mSupportRgba10101010ForHdr = supportRgba10101010ForHdr;
+}
+
void DeviceInfo::setSupportMixedColorSpaces(bool supportMixedColorSpaces) {
get()->mSupportMixedColorSpaces = supportMixedColorSpaces;
}
diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h
index a5a841e..fb58a69 100644
--- a/libs/hwui/DeviceInfo.h
+++ b/libs/hwui/DeviceInfo.h
@@ -69,6 +69,15 @@
return get()->mSupportFp16ForHdr;
};
+ static void setSupportRgba10101010ForHdr(bool supportRgba10101010ForHdr);
+ static bool isSupportRgba10101010ForHdr() {
+ if (!Properties::hdr10bitPlus) {
+ return false;
+ }
+
+ return get()->mSupportRgba10101010ForHdr;
+ };
+
static void setSupportMixedColorSpaces(bool supportMixedColorSpaces);
static bool isSupportMixedColorSpaces() { return get()->mSupportMixedColorSpaces; };
@@ -102,6 +111,7 @@
int mMaxTextureSize;
sk_sp<SkColorSpace> mWideColorSpace = SkColorSpace::MakeSRGB();
bool mSupportFp16ForHdr = false;
+ bool mSupportRgba10101010ForHdr = false;
bool mSupportMixedColorSpaces = false;
SkColorType mWideColorType = SkColorType::kN32_SkColorType;
int mDisplaysSize = 0;
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index 3d7e559..76a0a64 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -76,3 +76,10 @@
description: "Automatically animate all changes in HDR headroom"
bug: "314810174"
}
+
+flag {
+ name: "draw_region"
+ namespace: "core_graphics"
+ description: "Add canvas#drawRegion API"
+ bug: "318612129"
+}
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index 883f273..fb0cdb0 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -70,7 +70,6 @@
extern int register_android_graphics_fonts_FontFamily(JNIEnv* env);
extern int register_android_graphics_pdf_PdfDocument(JNIEnv* env);
extern int register_android_graphics_pdf_PdfEditor(JNIEnv* env);
-extern int register_android_graphics_pdf_PdfRenderer(JNIEnv* env);
extern int register_android_graphics_text_MeasuredText(JNIEnv* env);
extern int register_android_graphics_text_LineBreaker(JNIEnv *env);
extern int register_android_graphics_text_TextShaper(JNIEnv *env);
@@ -142,7 +141,6 @@
REG_JNI(register_android_graphics_fonts_FontFamily),
REG_JNI(register_android_graphics_pdf_PdfDocument),
REG_JNI(register_android_graphics_pdf_PdfEditor),
- REG_JNI(register_android_graphics_pdf_PdfRenderer),
REG_JNI(register_android_graphics_text_MeasuredText),
REG_JNI(register_android_graphics_text_LineBreaker),
REG_JNI(register_android_graphics_text_TextShaper),
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index d15b1680..d9e2c8c 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -860,7 +860,8 @@
static void android_view_ThreadedRenderer_initDisplayInfo(
JNIEnv* env, jclass, jint physicalWidth, jint physicalHeight, jfloat refreshRate,
jint wideColorDataspace, jlong appVsyncOffsetNanos, jlong presentationDeadlineNanos,
- jboolean supportFp16ForHdr, jboolean supportMixedColorSpaces) {
+ jboolean supportFp16ForHdr, jboolean supportRgba10101010ForHdr,
+ jboolean supportMixedColorSpaces) {
DeviceInfo::setWidth(physicalWidth);
DeviceInfo::setHeight(physicalHeight);
DeviceInfo::setRefreshRate(refreshRate);
@@ -868,6 +869,7 @@
DeviceInfo::setAppVsyncOffsetNanos(appVsyncOffsetNanos);
DeviceInfo::setPresentationDeadlineNanos(presentationDeadlineNanos);
DeviceInfo::setSupportFp16ForHdr(supportFp16ForHdr);
+ DeviceInfo::setSupportRgba10101010ForHdr(supportRgba10101010ForHdr);
DeviceInfo::setSupportMixedColorSpaces(supportMixedColorSpaces);
}
@@ -1020,7 +1022,7 @@
{"nSetForceDark", "(JI)V", (void*)android_view_ThreadedRenderer_setForceDark},
{"nSetDisplayDensityDpi", "(I)V",
(void*)android_view_ThreadedRenderer_setDisplayDensityDpi},
- {"nInitDisplayInfo", "(IIFIJJZZ)V", (void*)android_view_ThreadedRenderer_initDisplayInfo},
+ {"nInitDisplayInfo", "(IIFIJJZZZ)V", (void*)android_view_ThreadedRenderer_initDisplayInfo},
{"preload", "()V", (void*)android_view_ThreadedRenderer_preload},
{"isWebViewOverlaysEnabled", "()Z",
(void*)android_view_ThreadedRenderer_isWebViewOverlaysEnabled},
diff --git a/libs/hwui/jni/pdf/PdfRenderer.cpp b/libs/hwui/jni/pdf/PdfRenderer.cpp
deleted file mode 100644
index cc1f961..0000000
--- a/libs/hwui/jni/pdf/PdfRenderer.cpp
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "PdfUtils.h"
-
-#include "GraphicsJNI.h"
-#include "SkBitmap.h"
-#include "SkMatrix.h"
-#include "fpdfview.h"
-
-#include <vector>
-#include <utils/Log.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-namespace android {
-
-static const int RENDER_MODE_FOR_DISPLAY = 1;
-static const int RENDER_MODE_FOR_PRINT = 2;
-
-static struct {
- jfieldID x;
- jfieldID y;
-} gPointClassInfo;
-
-static jlong nativeOpenPageAndGetSize(JNIEnv* env, jclass thiz, jlong documentPtr,
- jint pageIndex, jobject outSize) {
- FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
-
- FPDF_PAGE page = FPDF_LoadPage(document, pageIndex);
- if (!page) {
- jniThrowException(env, "java/lang/IllegalStateException",
- "cannot load page");
- return -1;
- }
-
- double width = 0;
- double height = 0;
-
- int result = FPDF_GetPageSizeByIndex(document, pageIndex, &width, &height);
- if (!result) {
- jniThrowException(env, "java/lang/IllegalStateException",
- "cannot get page size");
- return -1;
- }
-
- env->SetIntField(outSize, gPointClassInfo.x, width);
- env->SetIntField(outSize, gPointClassInfo.y, height);
-
- return reinterpret_cast<jlong>(page);
-}
-
-static void nativeClosePage(JNIEnv* env, jclass thiz, jlong pagePtr) {
- FPDF_PAGE page = reinterpret_cast<FPDF_PAGE>(pagePtr);
- FPDF_ClosePage(page);
-}
-
-static void nativeRenderPage(JNIEnv* env, jclass thiz, jlong documentPtr, jlong pagePtr,
- jlong bitmapPtr, jint clipLeft, jint clipTop, jint clipRight, jint clipBottom,
- jlong transformPtr, jint renderMode) {
- FPDF_PAGE page = reinterpret_cast<FPDF_PAGE>(pagePtr);
-
- SkBitmap skBitmap;
- bitmap::toBitmap(bitmapPtr).getSkBitmap(&skBitmap);
-
- const int stride = skBitmap.width() * 4;
-
- FPDF_BITMAP bitmap = FPDFBitmap_CreateEx(skBitmap.width(), skBitmap.height(),
- FPDFBitmap_BGRA, skBitmap.getPixels(), stride);
-
- int renderFlags = FPDF_REVERSE_BYTE_ORDER;
- if (renderMode == RENDER_MODE_FOR_DISPLAY) {
- renderFlags |= FPDF_LCD_TEXT;
- } else if (renderMode == RENDER_MODE_FOR_PRINT) {
- renderFlags |= FPDF_PRINTING;
- }
-
- SkMatrix matrix = *reinterpret_cast<SkMatrix*>(transformPtr);
- SkScalar transformValues[6];
- if (!matrix.asAffine(transformValues)) {
- jniThrowException(env, "java/lang/IllegalArgumentException",
- "transform matrix has perspective. Only affine matrices are allowed.");
- return;
- }
-
- FS_MATRIX transform = {transformValues[SkMatrix::kAScaleX], transformValues[SkMatrix::kASkewY],
- transformValues[SkMatrix::kASkewX], transformValues[SkMatrix::kAScaleY],
- transformValues[SkMatrix::kATransX],
- transformValues[SkMatrix::kATransY]};
-
- FS_RECTF clip = {(float) clipLeft, (float) clipTop, (float) clipRight, (float) clipBottom};
-
- FPDF_RenderPageBitmapWithMatrix(bitmap, page, &transform, &clip, renderFlags);
-
- skBitmap.notifyPixelsChanged();
-}
-
-static const JNINativeMethod gPdfRenderer_Methods[] = {
- {"nativeCreate", "(IJ)J", (void*) nativeOpen},
- {"nativeClose", "(J)V", (void*) nativeClose},
- {"nativeGetPageCount", "(J)I", (void*) nativeGetPageCount},
- {"nativeScaleForPrinting", "(J)Z", (void*) nativeScaleForPrinting},
- {"nativeRenderPage", "(JJJIIIIJI)V", (void*) nativeRenderPage},
- {"nativeOpenPageAndGetSize", "(JILandroid/graphics/Point;)J", (void*) nativeOpenPageAndGetSize},
- {"nativeClosePage", "(J)V", (void*) nativeClosePage}
-};
-
-int register_android_graphics_pdf_PdfRenderer(JNIEnv* env) {
- int result = RegisterMethodsOrDie(
- env, "android/graphics/pdf/PdfRenderer", gPdfRenderer_Methods,
- NELEM(gPdfRenderer_Methods));
-
- jclass clazz = FindClassOrDie(env, "android/graphics/Point");
- gPointClassInfo.x = GetFieldIDOrDie(env, clazz, "x", "I");
- gPointClassInfo.y = GetFieldIDOrDie(env, clazz, "y", "I");
-
- return result;
-};
-
-};
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 326b6ed..99469d1 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -650,7 +650,11 @@
mSurfaceColorSpace = DeviceInfo::get()->getWideColorSpace();
break;
case ColorMode::Hdr:
- if (DeviceInfo::get()->isSupportFp16ForHdr()) {
+ if (DeviceInfo::get()->isSupportRgba10101010ForHdr()) {
+ mSurfaceColorType = SkColorType::kRGBA_10x6_SkColorType;
+ mSurfaceColorSpace = SkColorSpace::MakeRGB(
+ GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3);
+ } else if (DeviceInfo::get()->isSupportFp16ForHdr()) {
mSurfaceColorType = SkColorType::kRGBA_F16_SkColorType;
mSurfaceColorSpace = SkColorSpace::MakeSRGB();
} else {
@@ -675,7 +679,8 @@
if (mColorMode == ColorMode::Hdr || mColorMode == ColorMode::Hdr10) {
mTargetSdrHdrRatio = ratio;
- if (mColorMode == ColorMode::Hdr && DeviceInfo::get()->isSupportFp16ForHdr()) {
+ if (mColorMode == ColorMode::Hdr && DeviceInfo::get()->isSupportFp16ForHdr() &&
+ !DeviceInfo::get()->isSupportRgba10101010ForHdr()) {
mSurfaceColorSpace = SkColorSpace::MakeSRGB();
} else {
mSurfaceColorSpace = SkColorSpace::MakeRGB(
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 1d03301..abf64d0 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -411,7 +411,8 @@
// If the previous frame was dropped we don't need to hold onto it, so
// just keep using the previous frame's structure instead
- if (const auto reason = wasSkipped(mCurrentFrameInfo)) {
+ const auto reason = wasSkipped(mCurrentFrameInfo);
+ if (reason.has_value()) {
// Use the oldest skipped frame in case we skip more than a single frame
if (!mSkippedFrameInfo) {
switch (*reason) {
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index b5f7caa..0d0af11 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -25,6 +25,7 @@
#include <android/sync.h>
#include <gui/TraceUtils.h>
#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/gpu/ganesh/vk/GrVkBackendSemaphore.h>
#include <include/gpu/ganesh/vk/GrVkBackendSurface.h>
#include <include/gpu/ganesh/vk/GrVkDirectContext.h>
#include <ui/FatVector.h>
@@ -597,15 +598,14 @@
close(fence_clone);
sync_wait(bufferInfo->dequeue_fence, -1 /* forever */);
} else {
- GrBackendSemaphore backendSemaphore;
- backendSemaphore.initVulkan(semaphore);
+ GrBackendSemaphore beSemaphore = GrBackendSemaphores::MakeVk(semaphore);
// Skia will take ownership of the VkSemaphore and delete it once the wait
// has finished. The VkSemaphore also owns the imported fd, so it will
// close the fd when it is deleted.
- bufferInfo->skSurface->wait(1, &backendSemaphore);
+ bufferInfo->skSurface->wait(1, &beSemaphore);
// The following flush blocks the GPU immediately instead of waiting for
// other drawing ops. It seems dequeue_fence is not respected otherwise.
- // TODO: remove the flush after finding why backendSemaphore is not working.
+ // TODO: remove the flush after finding why beSemaphore is not working.
skgpu::ganesh::FlushAndSubmit(bufferInfo->skSurface.get());
}
}
@@ -626,7 +626,7 @@
SharedSemaphoreInfo(PFN_vkDestroySemaphore destroyFunction, VkDevice device,
VkSemaphore semaphore)
: mDestroyFunction(destroyFunction), mDevice(device), mSemaphore(semaphore) {
- mGrBackendSemaphore.initVulkan(semaphore);
+ mGrBackendSemaphore = GrBackendSemaphores::MakeVk(mSemaphore);
}
~SharedSemaphoreInfo() { mDestroyFunction(mDevice, mSemaphore, nullptr); }
@@ -798,8 +798,7 @@
return UNKNOWN_ERROR;
}
- GrBackendSemaphore beSemaphore;
- beSemaphore.initVulkan(semaphore);
+ GrBackendSemaphore beSemaphore = GrBackendSemaphores::MakeVk(semaphore);
// Skia will take ownership of the VkSemaphore and delete it once the wait has finished. The
// VkSemaphore also owns the imported fd, so it will close the fd when it is deleted.
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index 6dc45a6..6a32c5a 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -202,11 +202,13 @@
&& update.state.surfaceDrawn;
bool becomingVisible = wantSurfaceVisibleAndDrawn && !update.state.surfaceVisible;
bool becomingHidden = !wantSurfaceVisibleAndDrawn && update.state.surfaceVisible;
- if (update.state.surfaceControl != NULL && (becomingVisible || becomingHidden
- || (wantSurfaceVisibleAndDrawn && (update.state.dirty & (DIRTY_ALPHA
- | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER
- | DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID
- | DIRTY_ICON_STYLE))))) {
+ if (update.state.surfaceControl != NULL &&
+ (becomingVisible || becomingHidden ||
+ (wantSurfaceVisibleAndDrawn &&
+ (update.state.dirty &
+ (DIRTY_ALPHA | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER |
+ DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID | DIRTY_ICON_STYLE |
+ DIRTY_DRAW_DROP_SHADOW))))) {
needApplyTransaction = true;
if (wantSurfaceVisibleAndDrawn
@@ -235,13 +237,15 @@
update.state.transformationMatrix.dtdy);
}
- if (wantSurfaceVisibleAndDrawn
- && (becomingVisible
- || (update.state.dirty & (DIRTY_HOTSPOT | DIRTY_ICON_STYLE)))) {
+ if (wantSurfaceVisibleAndDrawn &&
+ (becomingVisible ||
+ (update.state.dirty &
+ (DIRTY_HOTSPOT | DIRTY_ICON_STYLE | DIRTY_DRAW_DROP_SHADOW)))) {
Parcel p;
p.writeInt32(static_cast<int32_t>(update.state.icon.style));
p.writeFloat(update.state.icon.hotSpotX);
p.writeFloat(update.state.icon.hotSpotY);
+ p.writeBool(update.state.icon.drawNativeDropShadow);
// Pass cursor metadata in the sprite surface so that when Android is running as a
// client OS (e.g. ARC++) the host OS can get the requested cursor metadata and
@@ -388,12 +392,13 @@
uint32_t dirty;
if (icon.isValid()) {
mLocked.state.icon.bitmap = icon.bitmap.copy(ANDROID_BITMAP_FORMAT_RGBA_8888);
- if (!mLocked.state.icon.isValid()
- || mLocked.state.icon.hotSpotX != icon.hotSpotX
- || mLocked.state.icon.hotSpotY != icon.hotSpotY) {
+ if (!mLocked.state.icon.isValid() || mLocked.state.icon.hotSpotX != icon.hotSpotX ||
+ mLocked.state.icon.hotSpotY != icon.hotSpotY ||
+ mLocked.state.icon.drawNativeDropShadow != icon.drawNativeDropShadow) {
mLocked.state.icon.hotSpotX = icon.hotSpotX;
mLocked.state.icon.hotSpotY = icon.hotSpotY;
- dirty = DIRTY_BITMAP | DIRTY_HOTSPOT;
+ mLocked.state.icon.drawNativeDropShadow = icon.drawNativeDropShadow;
+ dirty = DIRTY_BITMAP | DIRTY_HOTSPOT | DIRTY_DRAW_DROP_SHADOW;
} else {
dirty = DIRTY_BITMAP;
}
@@ -404,7 +409,7 @@
}
} else if (mLocked.state.icon.isValid()) {
mLocked.state.icon.bitmap.reset();
- dirty = DIRTY_BITMAP | DIRTY_HOTSPOT | DIRTY_ICON_STYLE;
+ dirty = DIRTY_BITMAP | DIRTY_HOTSPOT | DIRTY_ICON_STYLE | DIRTY_DRAW_DROP_SHADOW;
} else {
return; // setting to invalid icon and already invalid so nothing to do
}
diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h
index 04ecb38..35776e9 100644
--- a/libs/input/SpriteController.h
+++ b/libs/input/SpriteController.h
@@ -151,6 +151,7 @@
DIRTY_HOTSPOT = 1 << 6,
DIRTY_DISPLAY_ID = 1 << 7,
DIRTY_ICON_STYLE = 1 << 8,
+ DIRTY_DRAW_DROP_SHADOW = 1 << 9,
};
/* Describes the state of a sprite.
diff --git a/libs/input/SpriteIcon.h b/libs/input/SpriteIcon.h
index 0939af4..7d45d02 100644
--- a/libs/input/SpriteIcon.h
+++ b/libs/input/SpriteIcon.h
@@ -40,7 +40,7 @@
PointerIconStyle style{PointerIconStyle::TYPE_NULL};
float hotSpotX{};
float hotSpotY{};
- bool drawNativeDropShadow{false};
+ bool drawNativeDropShadow{};
inline bool isValid() const { return bitmap.isValid() && !bitmap.isEmpty(); }
diff --git a/location/api/system-current.txt b/location/api/system-current.txt
index 2e7a541..254d74a 100644
--- a/location/api/system-current.txt
+++ b/location/api/system-current.txt
@@ -616,8 +616,8 @@
@FlaggedApi(Flags.FLAG_NEW_GEOCODER) public abstract class GeocodeProviderBase {
ctor public GeocodeProviderBase(@NonNull android.content.Context, @NonNull String);
method @NonNull public final android.os.IBinder getBinder();
- method public abstract void onForwardGeocode(@NonNull android.location.provider.ForwardGeocodeRequest, @NonNull android.os.OutcomeReceiver<java.util.List<android.location.Address>,java.lang.Exception>);
- method public abstract void onReverseGeocode(@NonNull android.location.provider.ReverseGeocodeRequest, @NonNull android.os.OutcomeReceiver<java.util.List<android.location.Address>,java.lang.Exception>);
+ method public abstract void onForwardGeocode(@NonNull android.location.provider.ForwardGeocodeRequest, @NonNull android.os.OutcomeReceiver<java.util.List<android.location.Address>,java.lang.Throwable>);
+ method public abstract void onReverseGeocode(@NonNull android.location.provider.ReverseGeocodeRequest, @NonNull android.os.OutcomeReceiver<java.util.List<android.location.Address>,java.lang.Throwable>);
field public static final String ACTION_GEOCODE_PROVIDER = "com.android.location.service.GeocodeProvider";
}
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index 156be38..f33bcb7 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -11,7 +11,7 @@
name: "location_bypass"
namespace: "location"
description: "Enable location bypass appops behavior"
- bug: "301150056"
+ bug: "329151785"
}
flag {
diff --git a/location/java/android/location/provider/GeocodeProviderBase.java b/location/java/android/location/provider/GeocodeProviderBase.java
index e2c48b9..71644d07 100644
--- a/location/java/android/location/provider/GeocodeProviderBase.java
+++ b/location/java/android/location/provider/GeocodeProviderBase.java
@@ -104,14 +104,14 @@
*/
public abstract void onForwardGeocode(
@NonNull ForwardGeocodeRequest request,
- @NonNull OutcomeReceiver<List<Address>, Exception> callback);
+ @NonNull OutcomeReceiver<List<Address>, Throwable> callback);
/**
* Requests reverse geocoding of the given arguments. The given callback must be invoked once.
*/
public abstract void onReverseGeocode(
@NonNull ReverseGeocodeRequest request,
- @NonNull OutcomeReceiver<List<Address>, Exception> callback);
+ @NonNull OutcomeReceiver<List<Address>, Throwable> callback);
private class Service extends IGeocodeProvider.Stub {
@Override
@@ -145,7 +145,7 @@
}
}
- private static class SingleUseCallback implements OutcomeReceiver<List<Address>, Exception> {
+ private static class SingleUseCallback implements OutcomeReceiver<List<Address>, Throwable> {
private final AtomicReference<IGeocodeCallback> mCallback;
@@ -154,7 +154,7 @@
}
@Override
- public void onError(Exception e) {
+ public void onError(Throwable e) {
try {
Objects.requireNonNull(mCallback.getAndSet(null)).onError(e.toString());
} catch (RemoteException r) {
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index 72aaa35..55c8ed5 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -37,6 +37,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
+import android.os.Trace;
import android.view.Surface;
import dalvik.system.VMRuntime;
@@ -925,6 +926,7 @@
if (ir == null) {
return;
}
+ Trace.beginSection("android.media.ImageReader#postEventFromNative");
final Executor executor;
final OnImageAvailableListener listener;
@@ -948,6 +950,7 @@
}
});
}
+ Trace.endSection();
}
/**
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 9548525..f5dc6ea 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -2345,6 +2345,15 @@
throw new IllegalArgumentException("Can't use crypto and descrambler together!");
}
+ // at the moment no codecs support detachable surface
+ if (android.media.codec.Flags.nullOutputSurface()) {
+ // Detached surface flag is only meaningful if surface is null. Otherwise, it is
+ // ignored.
+ if (surface == null && (flags & CONFIGURE_FLAG_DETACHED_SURFACE) != 0) {
+ throw new IllegalArgumentException("Codec does not support detached surface");
+ }
+ }
+
String[] keys = null;
Object[] values = null;
@@ -2419,7 +2428,8 @@
* output.
*
* @throws IllegalStateException if the codec was not
- * configured in surface mode.
+ * configured in surface mode or if the codec does not support
+ * detaching the output surface.
* @see CONFIGURE_FLAG_DETACHED_SURFACE
*/
@FlaggedApi(FLAG_NULL_OUTPUT_SURFACE)
@@ -2429,6 +2439,7 @@
}
// note: we still have a surface in detached mode, so keep mHasSurface
// we also technically allow calling detachOutputSurface multiple times in a row
+ throw new IllegalStateException("codec does not support detaching output surface");
// native_detachSurface();
}
@@ -4750,6 +4761,9 @@
}
void setBufferInfo(MediaCodec.BufferInfo info) {
+ // since any of setBufferInfo(s) should translate to getBufferInfos,
+ // mBufferInfos needs to be reset for every setBufferInfo(s)
+ mBufferInfos.clear();
mPresentationTimeUs = info.presentationTimeUs;
mFlags = info.flags;
}
diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java
index 60ab1a4..a53a8ce 100644
--- a/media/java/android/media/audiopolicy/AudioMix.java
+++ b/media/java/android/media/audiopolicy/AudioMix.java
@@ -275,17 +275,23 @@
if (o == null || getClass() != o.getClass()) return false;
final AudioMix that = (AudioMix) o;
+ boolean tokenMatch = android.media.audiopolicy.Flags.audioMixOwnership()
+ ? Objects.equals(this.mToken, that.mToken)
+ : true;
return Objects.equals(this.mRouteFlags, that.mRouteFlags)
&& Objects.equals(this.mRule, that.mRule)
&& Objects.equals(this.mMixType, that.mMixType)
&& Objects.equals(this.mFormat, that.mFormat)
- && Objects.equals(this.mToken, that.mToken);
+ && tokenMatch;
}
/** @hide */
@Override
public int hashCode() {
- return Objects.hash(mRouteFlags, mRule, mMixType, mFormat, mToken);
+ if (android.media.audiopolicy.Flags.audioMixOwnership()) {
+ return Objects.hash(mRouteFlags, mRule, mMixType, mFormat, mToken);
+ }
+ return Objects.hash(mRouteFlags, mRule, mMixType, mFormat);
}
@Override
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 6cf9c6f..bf39425 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -109,5 +109,5 @@
name: "enable_null_session_in_media_browser_service"
namespace: "media_solutions"
description: "Enables apps owning a MediaBrowserService to disconnect all connected browsers."
- bug: "263520343"
+ bug: "185136506"
}
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
index 76664a6..ddcb25b 100644
--- a/media/java/android/media/tv/ad/TvAdManager.java
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -61,7 +61,6 @@
@FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
@SystemService(Context.TV_AD_SERVICE)
public final class TvAdManager {
- // TODO: implement more methods and unhide APIs.
private static final String TAG = "TvAdManager";
/**
diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
index 2fac8ce..dd2a534 100644
--- a/media/java/android/media/tv/ad/TvAdView.java
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -393,16 +393,12 @@
}
/**
- * Sets a listener to be invoked when an input event is not handled
- * by the TV AD service.
+ * Sets a listener to be invoked when an input event is not handled by the TV AD service.
*
* @param listener The callback to be invoked when the unhandled input event is received.
*/
- public void setOnUnhandledInputEventListener(
- @NonNull @CallbackExecutor Executor executor,
- @NonNull OnUnhandledInputEventListener listener) {
+ public void setOnUnhandledInputEventListener(@NonNull OnUnhandledInputEventListener listener) {
mOnUnhandledInputEventListener = listener;
- // TODO: handle CallbackExecutor
}
/**
@@ -441,6 +437,9 @@
/**
* Prepares the AD service of corresponding {@link TvAdService}.
*
+ * <p>This should be called before calling {@link #startAdService()}. Otherwise,
+ * {@link #startAdService()} is a no-op.
+ *
* @param serviceId the AD service ID, which can be found in TvAdServiceInfo#getId().
*/
public void prepareAdService(@NonNull String serviceId, @NonNull String type) {
@@ -455,6 +454,9 @@
/**
* Starts the AD service.
+ *
+ * <p>This should be called after calling {@link #prepareAdService(String, String)}. Otherwise,
+ * it's a no-op.
*/
public void startAdService() {
if (DEBUG) {
@@ -467,6 +469,8 @@
/**
* Stops the AD service.
+ *
+ * <p>It's a no-op if the service is not started.
*/
public void stopAdService() {
if (DEBUG) {
@@ -659,6 +663,11 @@
}
}
+ /** @hide */
+ public TvAdManager.Session getAdSession() {
+ return mSession;
+ }
+
private class MySessionCallback extends TvAdManager.SessionCallback {
final String mServiceId;
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index 382e65d..371e3d2 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -16,11 +16,13 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "ImageReader_JNI"
+#define ATRACE_TAG ATRACE_TAG_CAMERA
#include "android_media_Utils.h"
#include <cutils/atomic.h>
#include <utils/Log.h>
#include <utils/misc.h>
#include <utils/List.h>
+#include <utils/Trace.h>
#include <utils/String8.h>
#include <cstdio>
@@ -223,6 +225,7 @@
void JNIImageReaderContext::onFrameAvailable(const BufferItem& /*item*/)
{
+ ATRACE_CALL();
ALOGV("%s: frame available", __FUNCTION__);
bool needsDetach = false;
JNIEnv* env = getJNIEnv(&needsDetach);
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2CaptureRequestTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2CaptureRequestTest.java
index 47caf0a..ac12884 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2CaptureRequestTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2CaptureRequestTest.java
@@ -60,7 +60,7 @@
private static final int DEFAULT_SENSITIVITY = 100;
private static final long EXPOSURE_TIME_ERROR_MARGIN_NS = 100000L; // 100us, Approximation.
private static final float EXPOSURE_TIME_ERROR_MARGIN_RATE = 0.03f; // 3%, Approximation.
- private static final float SENSITIVITY_ERROR_MARGIN_RATE = 0.03f; // 3%, Approximation.
+ private static final float SENSITIVITY_ERROR_MARGIN_RATE = 0.06f; // 6%, Approximation.
private static final int DEFAULT_NUM_EXPOSURE_TIME_STEPS = 3;
private static final int DEFAULT_NUM_SENSITIVITY_STEPS = 16;
private static final int DEFAULT_SENSITIVITY_STEP_SIZE = 100;
diff --git a/mime/java-res/android.mime.types b/mime/java-res/android.mime.types
index b795560..5cf807d 100644
--- a/mime/java-res/android.mime.types
+++ b/mime/java-res/android.mime.types
@@ -80,6 +80,7 @@
?audio/aac-adts aac
?audio/ac3 ac3 a52
?audio/amr amr
+?audio/x-gsm gsm
?audio/imelody imy
?audio/midi rtttl xmf
?audio/mobile-xmf mxmf
@@ -103,6 +104,7 @@
?image/x-adobe-dng dng
?image/x-fuji-raf raf
?image/x-icon ico
+?image/x-jg art
?image/x-nikon-nrw nrw
?image/x-panasonic-rw2 rw2
?image/x-pentax-pef pef
@@ -117,7 +119,7 @@
?text/x-vcard vcf
?video/3gpp2 3gpp2 3gp2 3g2
-?video/3gpp 3gpp
+?video/3gpp 3gpp 3gp
?video/avi avi
?video/m4v m4v
?video/mp4 m4v f4v mp4v mpeg4
@@ -153,6 +155,7 @@
audio/x-mpegurl m3u m3u8
image/jpeg jpg
image/x-ms-bmp bmp
+image/x-photoshop psd
text/plain txt
text/x-c++hdr hpp
text/x-c++src cpp
diff --git a/nfc/Android.bp b/nfc/Android.bp
index 7dd16ba..7698e2b 100644
--- a/nfc/Android.bp
+++ b/nfc/Android.bp
@@ -76,6 +76,9 @@
"//apex_available:platform",
"com.android.nfcservices",
],
+ aconfig_declarations: [
+ "android.nfc.flags-aconfig",
+ ],
}
filegroup {
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 54f1421..9e0bb86 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -265,10 +265,11 @@
}
@FlaggedApi("android.nfc.nfc_read_polling_loop") public final class PollingFrame implements android.os.Parcelable {
- ctor public PollingFrame(int, @Nullable byte[], int, int);
+ ctor public PollingFrame(int, @Nullable byte[], int, int, boolean);
method public int describeContents();
method @NonNull public byte[] getData();
method public int getTimestamp();
+ method public boolean getTriggeredAutoTransact();
method public int getType();
method public int getVendorSpecificGain();
method public void writeToParcel(@NonNull android.os.Parcel, int);
diff --git a/nfc/java/android/nfc/cardemulation/PollingFrame.java b/nfc/java/android/nfc/cardemulation/PollingFrame.java
index 29d7bdf..7028c8f 100644
--- a/nfc/java/android/nfc/cardemulation/PollingFrame.java
+++ b/nfc/java/android/nfc/cardemulation/PollingFrame.java
@@ -133,12 +133,23 @@
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
public static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP";
+ /**
+ * KEY_POLLING_LOOP_TIMESTAMP is the Bundle key for whether this polling frame triggered
+ * autoTransact in the Bundle included in MSG_POLLING_LOOP.
+ *
+ * @hide
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final String KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT =
+ "android.nfc.cardemulation.TRIGGERED_AUTOTRANSACT";
+
@PollingFrameType
private final int mType;
private final byte[] mData;
private final int mGain;
private final int mTimestamp;
+ private final boolean mTriggeredAutoTransact;
public static final @NonNull Parcelable.Creator<PollingFrame> CREATOR =
new Parcelable.Creator<>() {
@@ -159,14 +170,17 @@
mData = (data == null) ? new byte[0] : data;
mGain = frame.getInt(KEY_POLLING_LOOP_GAIN, -1);
mTimestamp = frame.getInt(KEY_POLLING_LOOP_TIMESTAMP);
+ mTriggeredAutoTransact = frame.containsKey(KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT)
+ && frame.getBoolean(KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT);
}
public PollingFrame(@PollingFrameType int type, @Nullable byte[] data,
- int gain, int timestamp) {
+ int gain, int timestamp, boolean triggeredAutoTransact) {
mType = type;
mData = data == null ? new byte[0] : data;
mGain = gain;
mTimestamp = timestamp;
+ mTriggeredAutoTransact = triggeredAutoTransact;
}
/**
@@ -210,6 +224,14 @@
return mTimestamp;
}
+ /**
+ * Returns whether this frame triggered the device to automatically disable observe mode and
+ * allow one transaction.
+ */
+ public boolean getTriggeredAutoTransact() {
+ return mTriggeredAutoTransact;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -233,6 +255,7 @@
}
frame.putByteArray(KEY_POLLING_LOOP_DATA, getData());
frame.putInt(KEY_POLLING_LOOP_TIMESTAMP, getTimestamp());
+ frame.putBoolean(KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT, getTriggeredAutoTransact());
return frame;
}
diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
index 0a2619c..ba084c0 100644
--- a/nfc/java/android/nfc/flags.aconfig
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -2,6 +2,7 @@
flag {
name: "enable_nfc_mainline"
+ is_exported: true
namespace: "nfc"
description: "Flag for NFC mainline changes"
bug: "292140387"
@@ -9,6 +10,7 @@
flag {
name: "enable_nfc_reader_option"
+ is_exported: true
namespace: "nfc"
description: "Flag for NFC reader option API changes"
bug: "291187960"
@@ -16,6 +18,7 @@
flag {
name: "enable_nfc_user_restriction"
+ is_exported: true
namespace: "nfc"
description: "Flag for NFC user restriction"
bug: "291187960"
@@ -23,6 +26,7 @@
flag {
name: "nfc_observe_mode"
+ is_exported: true
namespace: "nfc"
description: "Enable NFC Observe Mode"
bug: "294217286"
@@ -30,6 +34,7 @@
flag {
name: "nfc_read_polling_loop"
+ is_exported: true
namespace: "nfc"
description: "Enable NFC Polling Loop Notifications"
bug: "294217286"
@@ -65,6 +70,7 @@
flag {
name: "enable_nfc_set_discovery_tech"
+ is_exported: true
namespace: "nfc"
description: "Flag for NFC set discovery tech API"
bug: "300351519"
@@ -72,6 +78,7 @@
flag {
name: "nfc_vendor_cmd"
+ is_exported: true
namespace: "nfc"
description: "Enable NFC vendor command support"
bug: "289879306"
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java
index 4500a22..5dcfd3b 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/DataBoostWebServiceFlow.java
@@ -40,7 +40,7 @@
*
* This can be called using the JavaScript below:
* <script type="text/javascript">
- * function getRequestedCapability(duration) {
+ * function getRequestedCapability() {
* DataBoostWebServiceFlow.getRequestedCapability();
* }
* </script>
@@ -57,6 +57,25 @@
*
* This can be called using the JavaScript below:
* <script type="text/javascript">
+ * function notifyPurchaseSuccessful(duration_ms_long = 0) {
+ * DataBoostWebServiceFlow.notifyPurchaseSuccessful(duration_ms_long);
+ * }
+ * </script>
+ *
+ * @param duration The duration for which the premium capability is purchased in milliseconds.
+ * NOTE: The duration parameter is not used.
+ */
+ @JavascriptInterface
+ public void notifyPurchaseSuccessful(long duration) {
+ mActivity.onPurchaseSuccessful();
+ }
+
+ /**
+ * Interface method allowing the carrier website to notify the slice purchase application of
+ * a successful premium capability purchase.
+ *
+ * This can be called using the JavaScript below:
+ * <script type="text/javascript">
* function notifyPurchaseSuccessful() {
* DataBoostWebServiceFlow.notifyPurchaseSuccessful();
* }
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 6019aa8..42d0cc4 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -52,12 +52,21 @@
<!-- Confirmation for associating an application with a companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] -->
<string name="title_app_streaming">Allow <strong><xliff:g id="app_name" example="Exo">%1$s</xliff:g></strong> to access this information from your phone</string>
+ <!-- Confirmation for associating an application with a companion device of APP_STREAMING profile (type) with mirroring enabled [CHAR LIMIT=NONE] -->
+ <string name="title_app_streaming_with_mirroring">Allow <strong><xliff:g id="app_name" example="Exo">%1$s</xliff:g></strong> to stream your phone\u2019s apps?</string>
+
+ <!-- Summary for associating an application with a companion device of APP_STREAMING profile [CHAR LIMIT=NONE] -->
+ <string name="summary_app_streaming">%1$s will have access to anything that’s visible or played on the phone, including audio, photos, passwords, and messages.<br/><br/>%1$s will be able to stream apps until you remove access to this permission.</string>
+
<!-- Title of the helper dialog for APP_STREAMING profile [CHAR LIMIT=30]. -->
<string name="helper_title_app_streaming">Cross-device services</string>
<!-- Description of the helper dialog for APP_STREAMING profile. [CHAR LIMIT=NONE] -->
<string name="helper_summary_app_streaming"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="display_name" example="Chromebook">%2$s</xliff:g> to stream apps between your devices</string>
+ <!-- Description of the helper dialog for APP_STREAMING profile with mirroring enabled. [CHAR LIMIT=NONE] -->
+ <string name="helper_summary_app_streaming_with_mirroring"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="display_name" example="Chromebook">%2$s</xliff:g> to display and stream apps between your devices</string>
+
<!-- ================= DEVICE_PROFILE_AUTOMOTIVE_PROJECTION ================= -->
<!-- Confirmation for associating an application with a companion device of AUTOMOTIVE_PROJECTION profile (type) [CHAR LIMIT=NONE] -->
@@ -85,6 +94,12 @@
<!-- Confirmation for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile (type) [CHAR LIMIT=NONE] -->
<string name="title_nearby_device_streaming">Allow <strong><xliff:g id="device_name" example="NearbyStreamer">%1$s</xliff:g></strong> to take this action?</string>
+ <!-- Confirmation for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile (type) with mirroring enabled [CHAR LIMIT=NONE] -->
+ <string name="title_nearby_device_streaming_with_mirroring">Allow <strong><xliff:g id="device_name" example="NearbyStreamer">%1$s</xliff:g></strong> to stream your phone\u2019s apps and system features?</string>
+
+ <!-- Summary for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile [CHAR LIMIT=NONE] -->
+ <string name="summary_nearby_device_streaming">%1$s will have access to anything that’s visible or played on your phone, including audio, photos, payment info, passwords, and messages.<br/><br/>%1$s will be able to stream apps and system features until you remove access to this permission.</string>
+
<!-- Description of the helper dialog for NEARBY_DEVICE_STREAMING profile. [CHAR LIMIT=NONE] -->
<string name="helper_summary_nearby_device_streaming"><xliff:g id="app_name" example="NearbyStreamerApp">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_name" example="NearbyDevice">%2$s</xliff:g> to stream apps and other system features to nearby devices</string>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 4c1f631..1231b63 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -179,7 +179,7 @@
// onActivityResult() after the association is created.
private @Nullable DeviceFilterPair<?> mSelectedDevice;
- private LinearLayoutManager mPermissionsLayoutManager = new LinearLayoutManager(this);
+ private final LinearLayoutManager mPermissionsLayoutManager = new LinearLayoutManager(this);
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -484,10 +484,18 @@
}
title = getHtmlFromResources(this, PROFILE_TITLES.get(deviceProfile), deviceName);
+
+ if (PROFILE_SUMMARIES.containsKey(deviceProfile)) {
+ final int summaryResourceId = PROFILE_SUMMARIES.get(deviceProfile);
+ final Spanned summary = getHtmlFromResources(this, summaryResourceId,
+ deviceName);
+ mSummary.setText(summary);
+ } else {
+ mSummary.setVisibility(View.GONE);
+ }
+
setupPermissionList(deviceProfile);
- // Summary is not needed for selfManaged dialog.
- mSummary.setVisibility(View.GONE);
mTitle.setText(title);
mVendorHeaderName.setText(vendorName);
mVendorHeader.setVisibility(View.VISIBLE);
@@ -692,6 +700,11 @@
private void setupPermissionList(String deviceProfile) {
final List<Integer> permissionTypes = new ArrayList<>(
PROFILE_PERMISSIONS.get(deviceProfile));
+ if (permissionTypes.isEmpty()) {
+ // Nothing to do if there are no permission types.
+ return;
+ }
+
mPermissionListAdapter.setPermissionType(permissionTypes);
mPermissionListRecyclerView.setAdapter(mPermissionListAdapter);
mPermissionListRecyclerView.setLayoutManager(mPermissionsLayoutManager);
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
index 23a11d6..dc68bcc 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
@@ -27,11 +27,13 @@
import static java.util.Collections.unmodifiableMap;
import static java.util.Collections.unmodifiableSet;
+import android.companion.virtual.flags.Flags;
import android.os.Build;
import android.util.ArrayMap;
import android.util.ArraySet;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -122,10 +124,19 @@
static final Map<String, Integer> PROFILE_TITLES;
static {
final Map<String, Integer> map = new ArrayMap<>();
- map.put(DEVICE_PROFILE_APP_STREAMING, R.string.title_app_streaming);
+ if (Flags.interactiveScreenMirror()) {
+ map.put(DEVICE_PROFILE_APP_STREAMING, R.string.title_app_streaming_with_mirroring);
+ } else {
+ map.put(DEVICE_PROFILE_APP_STREAMING, R.string.title_app_streaming);
+ }
map.put(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, R.string.title_automotive_projection);
map.put(DEVICE_PROFILE_COMPUTER, R.string.title_computer);
- map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, R.string.title_nearby_device_streaming);
+ if (Flags.interactiveScreenMirror()) {
+ map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
+ R.string.title_nearby_device_streaming_with_mirroring);
+ } else {
+ map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, R.string.title_nearby_device_streaming);
+ }
map.put(DEVICE_PROFILE_WATCH, R.string.confirmation_title);
map.put(DEVICE_PROFILE_GLASSES, R.string.confirmation_title_glasses);
map.put(null, R.string.confirmation_title);
@@ -138,6 +149,11 @@
final Map<String, Integer> map = new ArrayMap<>();
map.put(DEVICE_PROFILE_WATCH, R.string.summary_watch);
map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses);
+ if (Flags.interactiveScreenMirror()) {
+ map.put(DEVICE_PROFILE_APP_STREAMING, R.string.summary_app_streaming);
+ map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
+ R.string.summary_nearby_device_streaming);
+ }
map.put(null, R.string.summary_generic);
PROFILE_SUMMARIES = unmodifiableMap(map);
@@ -146,11 +162,16 @@
static final Map<String, List<Integer>> PROFILE_PERMISSIONS;
static {
final Map<String, List<Integer>> map = new ArrayMap<>();
- map.put(DEVICE_PROFILE_APP_STREAMING, Arrays.asList(PERMISSION_APP_STREAMING));
map.put(DEVICE_PROFILE_COMPUTER, Arrays.asList(
PERMISSION_NOTIFICATION_LISTENER_ACCESS, PERMISSION_STORAGE));
- map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
- Arrays.asList(PERMISSION_NEARBY_DEVICE_STREAMING));
+ if (Flags.interactiveScreenMirror()) {
+ map.put(DEVICE_PROFILE_APP_STREAMING, Collections.emptyList());
+ map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, Collections.emptyList());
+ } else {
+ map.put(DEVICE_PROFILE_APP_STREAMING, Arrays.asList(PERMISSION_APP_STREAMING));
+ map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
+ Arrays.asList(PERMISSION_NEARBY_DEVICE_STREAMING));
+ }
if (Build.VERSION.SDK_INT > UPSIDE_DOWN_CAKE) {
map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATIONS, PERMISSION_PHONE,
PERMISSION_CALL_LOGS, PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_CALENDAR,
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
index 8f32dbb..fe0e021 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
@@ -26,6 +26,7 @@
import android.annotation.Nullable;
import android.companion.AssociationRequest;
+import android.companion.virtual.flags.Flags;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
@@ -129,7 +130,9 @@
case DEVICE_PROFILE_APP_STREAMING:
title = getHtmlFromResources(getContext(), R.string.helper_title_app_streaming);
summary = getHtmlFromResources(
- getContext(), R.string.helper_summary_app_streaming, title, displayName);
+ getContext(), Flags.interactiveScreenMirror()
+ ? R.string.helper_summary_app_streaming_with_mirroring
+ : R.string.helper_summary_app_streaming, title, displayName);
break;
case DEVICE_PROFILE_COMPUTER:
diff --git a/packages/CrashRecovery/OWNERS b/packages/CrashRecovery/OWNERS
index daa0211..8337fd2 100644
--- a/packages/CrashRecovery/OWNERS
+++ b/packages/CrashRecovery/OWNERS
@@ -1,3 +1 @@
-ancr@google.com
-harshitmahajan@google.com
-robertogil@google.com
+include /services/core/java/com/android/server/crashrecovery/OWNERS
\ No newline at end of file
diff --git a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
index 5d71b7d..37b5d40 100644
--- a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
@@ -38,15 +38,15 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
-import android.util.BackgroundThread;
-import android.util.LongArrayQueue;
import android.util.Slog;
import android.util.Xml;
+import android.utils.BackgroundThread;
+import android.utils.LongArrayQueue;
+import android.utils.XmlUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
index 9217e70..d0fee44 100644
--- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
@@ -29,7 +29,6 @@
import android.content.pm.VersionedPackage;
import android.os.Build;
import android.os.Environment;
-import android.os.FileUtils;
import android.os.PowerManager;
import android.os.RecoverySystem;
import android.os.SystemClock;
@@ -42,10 +41,11 @@
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
+import android.utils.ArrayUtils;
+import android.utils.FileUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
import com.android.server.PackageWatchdog.FailureReasons;
import com.android.server.PackageWatchdog.PackageHealthObserver;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
@@ -707,7 +707,7 @@
if (pm.getModuleInfo(packageName, 0) != null) {
return true;
}
- } catch (PackageManager.NameNotFoundException ignore) {
+ } catch (PackageManager.NameNotFoundException | IllegalStateException ignore) {
}
return isPersistentSystemApp(packageName);
diff --git a/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java b/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java
new file mode 100644
index 0000000..fa4d6af
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/ArrayUtils.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.utils;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.io.File;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Copied over from frameworks/base/core/java/com/android/internal/util/ArrayUtils.java
+ *
+ * @hide
+ */
+public class ArrayUtils {
+ private ArrayUtils() { /* cannot be instantiated */ }
+ public static final File[] EMPTY_FILE = new File[0];
+
+
+ /**
+ * Return first index of {@code value} in {@code array}, or {@code -1} if
+ * not found.
+ */
+ public static <T> int indexOf(@Nullable T[] array, T value) {
+ if (array == null) return -1;
+ for (int i = 0; i < array.length; i++) {
+ if (Objects.equals(array[i], value)) return i;
+ }
+ return -1;
+ }
+
+ /** @hide */
+ public static @NonNull File[] defeatNullable(@Nullable File[] val) {
+ return (val != null) ? val : EMPTY_FILE;
+ }
+
+ /**
+ * Checks if given array is null or has zero elements.
+ */
+ public static boolean isEmpty(@Nullable int[] array) {
+ return array == null || array.length == 0;
+ }
+
+ /**
+ * True if the byte array is null or has length 0.
+ */
+ public static boolean isEmpty(@Nullable byte[] array) {
+ return array == null || array.length == 0;
+ }
+
+ /**
+ * Converts from List of bytes to byte array
+ * @param list
+ * @return byte[]
+ */
+ public static byte[] toPrimitive(List<byte[]> list) {
+ if (list.size() == 0) {
+ return new byte[0];
+ }
+ int byteLen = list.get(0).length;
+ byte[] array = new byte[list.size() * byteLen];
+ for (int i = 0; i < list.size(); i++) {
+ for (int j = 0; j < list.get(i).length; j++) {
+ array[i * byteLen + j] = list.get(i)[j];
+ }
+ }
+ return array;
+ }
+
+ /**
+ * Adds value to given array if not already present, providing set-like
+ * behavior.
+ */
+ public static @NonNull int[] appendInt(@Nullable int[] cur, int val) {
+ return appendInt(cur, val, false);
+ }
+
+ /**
+ * Adds value to given array.
+ */
+ public static @NonNull int[] appendInt(@Nullable int[] cur, int val,
+ boolean allowDuplicates) {
+ if (cur == null) {
+ return new int[] { val };
+ }
+ final int n = cur.length;
+ if (!allowDuplicates) {
+ for (int i = 0; i < n; i++) {
+ if (cur[i] == val) {
+ return cur;
+ }
+ }
+ }
+ int[] ret = new int[n + 1];
+ System.arraycopy(cur, 0, ret, 0, n);
+ ret[n] = val;
+ return ret;
+ }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java b/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
similarity index 99%
rename from packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java
rename to packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
index a6ae68f..afcf689 100644
--- a/packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java
+++ b/packages/CrashRecovery/services/java/com/android/utils/BackgroundThread.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.util;
+package android.utils;
import android.annotation.NonNull;
import android.os.Handler;
diff --git a/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java b/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java
new file mode 100644
index 0000000..e4923bf
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/FileUtils.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.utils;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Bits and pieces copied from hidden API of android.os.FileUtils.
+ *
+ * @hide
+ */
+public class FileUtils {
+ /**
+ * Read a text file into a String, optionally limiting the length.
+ *
+ * @param file to read (will not seek, so things like /proc files are OK)
+ * @param max length (positive for head, negative of tail, 0 for no limit)
+ * @param ellipsis to add of the file was truncated (can be null)
+ * @return the contents of the file, possibly truncated
+ * @throws IOException if something goes wrong reading the file
+ * @hide
+ */
+ public static @Nullable String readTextFile(@Nullable File file, @Nullable int max,
+ @Nullable String ellipsis) throws IOException {
+ InputStream input = new FileInputStream(file);
+ // wrapping a BufferedInputStream around it because when reading /proc with unbuffered
+ // input stream, bytes read not equal to buffer size is not necessarily the correct
+ // indication for EOF; but it is true for BufferedInputStream due to its implementation.
+ BufferedInputStream bis = new BufferedInputStream(input);
+ try {
+ long size = file.length();
+ if (max > 0 || (size > 0 && max == 0)) { // "head" mode: read the first N bytes
+ if (size > 0 && (max == 0 || size < max)) max = (int) size;
+ byte[] data = new byte[max + 1];
+ int length = bis.read(data);
+ if (length <= 0) return "";
+ if (length <= max) return new String(data, 0, length);
+ if (ellipsis == null) return new String(data, 0, max);
+ return new String(data, 0, max) + ellipsis;
+ } else if (max < 0) { // "tail" mode: keep the last N
+ int len;
+ boolean rolled = false;
+ byte[] last = null;
+ byte[] data = null;
+ do {
+ if (last != null) rolled = true;
+ byte[] tmp = last;
+ last = data;
+ data = tmp;
+ if (data == null) data = new byte[-max];
+ len = bis.read(data);
+ } while (len == data.length);
+
+ if (last == null && len <= 0) return "";
+ if (last == null) return new String(data, 0, len);
+ if (len > 0) {
+ rolled = true;
+ System.arraycopy(last, len, last, 0, last.length - len);
+ System.arraycopy(data, 0, last, last.length - len, len);
+ }
+ if (ellipsis == null || !rolled) return new String(last);
+ return ellipsis + new String(last);
+ } else { // "cat" mode: size unknown, read it all in streaming fashion
+ ByteArrayOutputStream contents = new ByteArrayOutputStream();
+ int len;
+ byte[] data = new byte[1024];
+ do {
+ len = bis.read(data);
+ if (len > 0) contents.write(data, 0, len);
+ } while (len == data.length);
+ return contents.toString();
+ }
+ } finally {
+ bis.close();
+ input.close();
+ }
+ }
+
+ /**
+ * Perform an fsync on the given FileOutputStream. The stream at this
+ * point must be flushed but not yet closed.
+ *
+ * @hide
+ */
+ public static boolean sync(FileOutputStream stream) {
+ try {
+ if (stream != null) {
+ stream.getFD().sync();
+ }
+ return true;
+ } catch (IOException e) {
+ }
+ return false;
+ }
+
+ /**
+ * List the files in the directory or return empty file.
+ *
+ * @hide
+ */
+ public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) {
+ return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles())
+ : ArrayUtils.EMPTY_FILE;
+ }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java b/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
similarity index 98%
rename from packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java
rename to packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
index 948ebcca..fdb15e2 100644
--- a/packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java
+++ b/packages/CrashRecovery/services/java/com/android/utils/HandlerExecutor.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.util;
+package android.utils;
import android.annotation.NonNull;
import android.os.Handler;
diff --git a/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java b/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java
new file mode 100644
index 0000000..5cdc253
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/LongArrayQueue.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.utils;
+
+import libcore.util.EmptyArray;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Copied from frameworks/base/core/java/android/util/LongArrayQueue.java
+ *
+ * @hide
+ */
+public class LongArrayQueue {
+
+ private long[] mValues;
+ private int mSize;
+ private int mHead;
+ private int mTail;
+
+ private long[] newUnpaddedLongArray(int num) {
+ return new long[num];
+ }
+ /**
+ * Initializes a queue with the given starting capacity.
+ *
+ * @param initialCapacity the capacity.
+ */
+ public LongArrayQueue(int initialCapacity) {
+ if (initialCapacity == 0) {
+ mValues = EmptyArray.LONG;
+ } else {
+ mValues = newUnpaddedLongArray(initialCapacity);
+ }
+ mSize = 0;
+ mHead = mTail = 0;
+ }
+
+ /**
+ * Initializes a queue with default starting capacity.
+ */
+ public LongArrayQueue() {
+ this(16);
+ }
+
+ /** @hide */
+ public static int growSize(int currentSize) {
+ return currentSize <= 4 ? 8 : currentSize * 2;
+ }
+
+ private void grow() {
+ if (mSize < mValues.length) {
+ throw new IllegalStateException("Queue not full yet!");
+ }
+ final int newSize = growSize(mSize);
+ final long[] newArray = newUnpaddedLongArray(newSize);
+ final int r = mValues.length - mHead; // Number of elements on and to the right of head.
+ System.arraycopy(mValues, mHead, newArray, 0, r);
+ System.arraycopy(mValues, 0, newArray, r, mHead);
+ mValues = newArray;
+ mHead = 0;
+ mTail = mSize;
+ }
+
+ /**
+ * Returns the number of elements in the queue.
+ */
+ public int size() {
+ return mSize;
+ }
+
+ /**
+ * Removes all elements from this queue.
+ */
+ public void clear() {
+ mSize = 0;
+ mHead = mTail = 0;
+ }
+
+ /**
+ * Adds a value to the tail of the queue.
+ *
+ * @param value the value to be added.
+ */
+ public void addLast(long value) {
+ if (mSize == mValues.length) {
+ grow();
+ }
+ mValues[mTail] = value;
+ mTail = (mTail + 1) % mValues.length;
+ mSize++;
+ }
+
+ /**
+ * Removes an element from the head of the queue.
+ *
+ * @return the element at the head of the queue.
+ * @throws NoSuchElementException if the queue is empty.
+ */
+ public long removeFirst() {
+ if (mSize == 0) {
+ throw new NoSuchElementException("Queue is empty!");
+ }
+ final long ret = mValues[mHead];
+ mHead = (mHead + 1) % mValues.length;
+ mSize--;
+ return ret;
+ }
+
+ /**
+ * Returns the element at the given position from the head of the queue, where 0 represents the
+ * head of the queue.
+ *
+ * @param position the position from the head of the queue.
+ * @return the element found at the given position.
+ * @throws IndexOutOfBoundsException if {@code position} < {@code 0} or
+ * {@code position} >= {@link #size()}
+ */
+ public long get(int position) {
+ if (position < 0 || position >= mSize) {
+ throw new IndexOutOfBoundsException("Index " + position
+ + " not valid for a queue of size " + mSize);
+ }
+ final int index = (mHead + position) % mValues.length;
+ return mValues[index];
+ }
+
+ /**
+ * Returns the element at the head of the queue, without removing it.
+ *
+ * @return the element at the head of the queue.
+ * @throws NoSuchElementException if the queue is empty
+ */
+ public long peekFirst() {
+ if (mSize == 0) {
+ throw new NoSuchElementException("Queue is empty!");
+ }
+ return mValues[mHead];
+ }
+
+ /**
+ * Returns the element at the tail of the queue.
+ *
+ * @return the element at the tail of the queue.
+ * @throws NoSuchElementException if the queue is empty.
+ */
+ public long peekLast() {
+ if (mSize == 0) {
+ throw new NoSuchElementException("Queue is empty!");
+ }
+ final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1;
+ return mValues[index];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ if (mSize <= 0) {
+ return "{}";
+ }
+
+ final StringBuilder buffer = new StringBuilder(mSize * 64);
+ buffer.append('{');
+ buffer.append(get(0));
+ for (int i = 1; i < mSize; i++) {
+ buffer.append(", ");
+ buffer.append(get(i));
+ }
+ buffer.append('}');
+ return buffer.toString();
+ }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java b/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java
new file mode 100644
index 0000000..dbbef61
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/utils/XmlUtils.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.utils;
+
+import android.annotation.NonNull;
+import android.system.ErrnoException;
+import android.system.Os;
+
+import com.android.modules.utils.TypedXmlPullParser;
+
+import libcore.util.XmlObjectFactory;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Copied over partly from frameworks/base/core/java/com/android/internal/util/XmlUtils.java
+ *
+ * @hide
+ */
+public class XmlUtils {
+
+ private static final String STRING_ARRAY_SEPARATOR = ":";
+
+ /** @hide */
+ public static final void beginDocument(XmlPullParser parser, String firstElementName)
+ throws XmlPullParserException, IOException {
+ int type;
+ while ((type = parser.next()) != parser.START_TAG
+ && type != parser.END_DOCUMENT) {
+ // Do nothing
+ }
+
+ if (type != parser.START_TAG) {
+ throw new XmlPullParserException("No start tag found");
+ }
+
+ if (!parser.getName().equals(firstElementName)) {
+ throw new XmlPullParserException("Unexpected start tag: found " + parser.getName()
+ + ", expected " + firstElementName);
+ }
+ }
+
+ /** @hide */
+ public static boolean nextElementWithin(XmlPullParser parser, int outerDepth)
+ throws IOException, XmlPullParserException {
+ for (;;) {
+ int type = parser.next();
+ if (type == XmlPullParser.END_DOCUMENT
+ || (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth)) {
+ return false;
+ }
+ if (type == XmlPullParser.START_TAG
+ && parser.getDepth() == outerDepth + 1) {
+ return true;
+ }
+ }
+ }
+
+ private static XmlPullParser newPullParser() {
+ try {
+ XmlPullParser parser = XmlObjectFactory.newXmlPullParser();
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true);
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+ return parser;
+ } catch (XmlPullParserException e) {
+ throw new AssertionError();
+ }
+ }
+
+ /** @hide */
+ public static @NonNull TypedXmlPullParser resolvePullParser(@NonNull InputStream in)
+ throws IOException {
+ final byte[] magic = new byte[4];
+ if (in instanceof FileInputStream) {
+ try {
+ Os.pread(((FileInputStream) in).getFD(), magic, 0, magic.length, 0);
+ } catch (ErrnoException e) {
+ throw e.rethrowAsIOException();
+ }
+ } else {
+ if (!in.markSupported()) {
+ in = new BufferedInputStream(in);
+ }
+ in.mark(8);
+ in.read(magic);
+ in.reset();
+ }
+
+ final TypedXmlPullParser xml;
+ xml = (TypedXmlPullParser) newPullParser();
+ try {
+ xml.setInput(in, "UTF_8");
+ } catch (XmlPullParserException e) {
+ throw new IOException(e);
+ }
+ return xml;
+ }
+}
diff --git a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one_dark.xml b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_middle.xml
similarity index 60%
rename from packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one_dark.xml
rename to packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_middle.xml
index 39f49ca..781373d 100644
--- a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one_dark.xml
+++ b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_middle.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2023 The Android Open Source Project
+ ~ Copyright (C) 2024 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
@@ -15,16 +15,10 @@
~ limitations under the License.
-->
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools" tools:ignore="NewApi"
- android:color="@android:color/transparent">
- <item
- android:bottom="1dp"
- android:shape="rectangle"
- android:top="1dp">
- <shape>
- <corners android:radius="28dp" />
- <solid android:color="@android:color/system_surface_container_high_dark" />
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:tools="http://schemas.android.com/tools" tools:ignore="NewApi">
+ <shape android:shape="rectangle">
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainer" />
</shape>
- </item>
-</ripple>
\ No newline at end of file
+</inset>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
index f13402c..f28c354 100644
--- a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
+++ b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
@@ -15,16 +15,12 @@
~ limitations under the License.
-->
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools" tools:ignore="NewApi"
- android:color="@android:color/transparent">
- <item
- android:bottom="1dp"
- android:shape="rectangle"
- android:top="1dp">
- <shape>
- <corners android:radius="4dp" />
- <solid android:color="@color/dropdown_container" />
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:tools="http://schemas.android.com/tools" tools:ignore="NewApi">
+ <shape android:shape="rectangle">
+ <corners android:topLeftRadius="4dp" android:topRightRadius="4dp"
+ android:bottomLeftRadius="0dp" android:bottomRightRadius="0dp"/>
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainer" />
</shape>
- </item>
-</ripple>
\ No newline at end of file
+</inset>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/more_options_list_item.xml b/packages/CredentialManager/res/drawable/more_options_list_item.xml
index d7b509e..3f9d815 100644
--- a/packages/CredentialManager/res/drawable/more_options_list_item.xml
+++ b/packages/CredentialManager/res/drawable/more_options_list_item.xml
@@ -15,17 +15,16 @@
~ limitations under the License.
-->
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools" tools:ignore="NewApi"
- android:color="@android:color/transparent">
- <item
- android:bottom="1dp"
- android:shape="rectangle"
- android:top="1dp">
- <shape>
- <corners android:bottomLeftRadius="4dp"
- android:bottomRightRadius="4dp"/>
- <solid android:color="@color/sign_in_options_container" />
- </shape>
- </item>
-</ripple>
\ No newline at end of file
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:tools="http://schemas.android.com/tools" tools:ignore="NewApi"
+ android:insetLeft="-2dp"
+ android:insetRight="-2dp"
+ android:insetBottom="-2dp">
+ <shape>
+ <corners android:topLeftRadius="0dp" android:topRightRadius="0dp"
+ android:bottomLeftRadius="4dp" android:bottomRightRadius="4dp"/>
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainer"/>
+ <stroke android:color="?androidprv:attr/materialColorOutlineVariant" android:width="1dp"/>
+ </shape>
+</inset>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
index 910ff96..7f09dd5 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
@@ -14,30 +14,52 @@
~ limitations under the License.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@android:id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="@dimen/dropdown_touch_target_min_height"
android:orientation="horizontal"
- android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
android:elevation="3dp">
- <ImageView
- android:id="@android:id/icon1"
+ <LinearLayout
+ android:id="@+id/credential_card"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_centerVertical="true"
- android:layout_alignParentStart="true"
- android:contentDescription="@string/more_options_content_description"
- android:background="@null"/>
- <TextView
- android:id="@android:id/text1"
- android:layout_width="wrap_content"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@android:id/icon1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:paddingLeft="@dimen/autofill_view_left_padding"
+ android:src="@drawable/more_horiz_24px"
+ android:tint="?androidprv:attr/materialColorOnSurface"
+ android:layout_alignParentStart="true"
+ android:contentDescription="@string/more_options_content_description"
+ android:background="@null"/>
+
+ <LinearLayout
+ android:id="@+id/text_container"
+ android:layout_width="@dimen/autofill_dropdown_textview_max_width"
android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_toEndOf="@android:id/icon1"
- android:minWidth="@dimen/autofill_dropdown_textview_min_width"
- android:maxWidth="@dimen/autofill_dropdown_textview_max_width"
- style="@style/autofill.TextTitle"/>
+ android:paddingLeft="@dimen/autofill_view_left_padding"
+ android:paddingRight="@dimen/autofill_view_right_padding"
+ android:paddingTop="@dimen/more_options_item_vertical_padding"
+ android:paddingBottom="@dimen/more_options_item_vertical_padding"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@android:id/text1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:textColor="?androidprv:attr/materialColorOnSurface"
+ android:layout_toEndOf="@android:id/icon1"
+ style="@style/autofill.TextTitle"/>
+ </LinearLayout>
+
+ </LinearLayout>
</RelativeLayout>
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
index 4bf0e99..08948d7 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -14,37 +14,60 @@
~ limitations under the License.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@android:id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="@dimen/dropdown_touch_target_min_height"
- android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
android:elevation="3dp">
- <ImageView
- android:id="@android:id/icon1"
+ <LinearLayout
+ android:id="@+id/credential_card"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_centerVertical="true"
- android:layout_alignParentStart="true"
- android:background="@null"/>
- <TextView
- android:id="@android:id/text1"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_toEndOf="@android:id/icon1"
- android:minWidth="@dimen/autofill_dropdown_textview_min_width"
- android:maxWidth="@dimen/autofill_dropdown_textview_max_width"
- style="@style/autofill.TextTitle"/>
- <TextView
- android:id="@android:id/text2"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@android:id/text1"
- android:layout_toEndOf="@android:id/icon1"
- android:minWidth="@dimen/autofill_dropdown_textview_min_width"
- android:maxWidth="@dimen/autofill_dropdown_textview_max_width"
- style="@style/autofill.TextSubtitle"/>
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@android:id/icon1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_alignParentStart="true"
+ android:paddingLeft="@dimen/autofill_view_left_padding"
+ app:tint="?androidprv:attr/materialColorOnSurface"
+ android:background="@null"/>
+
+ <LinearLayout
+ android:id="@+id/text_container"
+ android:layout_width="@dimen/autofill_dropdown_textview_max_width"
+ android:layout_height="wrap_content"
+ android:paddingLeft="@dimen/autofill_view_left_padding"
+ android:paddingRight="@dimen/autofill_view_right_padding"
+ android:paddingTop="@dimen/autofill_view_top_padding"
+ android:paddingBottom="@dimen/autofill_view_bottom_padding"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@android:id/text1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_toEndOf="@android:id/icon1"
+ android:textColor="?androidprv:attr/materialColorOnSurface"
+ style="@style/autofill.TextTitle"/>
+
+ <TextView
+ android:id="@android:id/text2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/text1"
+ android:layout_toEndOf="@android:id/icon1"
+ android:textColor="?androidprv:attr/materialColorOnSurfaceVariant"
+ style="@style/autofill.TextSubtitle"/>
+
+ </LinearLayout>
+
+ </LinearLayout>
</RelativeLayout>
diff --git a/packages/CredentialManager/res/values-af/strings.xml b/packages/CredentialManager/res/values-af/strings.xml
index b9ed4a2..5d9295b 100644
--- a/packages/CredentialManager/res/values-af/strings.xml
+++ b/packages/CredentialManager/res/values-af/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Wagwoorde sal steeds saam met toegangsleutels beskikbaar wees terwyl ons na ’n wagwoordlose toekoms beweeg."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Kies waar om jou <xliff:g id="CREATETYPES">%1$s</xliff:g> te stoor"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Kies ’n wagwoordbestuurder om jou inligting te stoor en volgende keer vinniger aan te meld"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Wil jy toegangsleutel skep om by <xliff:g id="APPNAME">%1$s</xliff:g> aan te meld?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Wil jy wagwoord stoor om by <xliff:g id="APPNAME">%1$s</xliff:g> aan te meld?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Stoor aanmeldinligting vir <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"toegangsleutel"</string>
<string name="password" msgid="6738570945182936667">"wagwoord"</string>
diff --git a/packages/CredentialManager/res/values-am/strings.xml b/packages/CredentialManager/res/values-am/strings.xml
index e1ded6a..2e8021d 100644
--- a/packages/CredentialManager/res/values-am/strings.xml
+++ b/packages/CredentialManager/res/values-am/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"ወደ የይለፍ ቃል የሌለው ወደፊት ስንሄድ የይለፍ ቃላት ከይለፍ ቁልፎች ጎን ለጎን ይገኛሉ።"</string>
<string name="choose_provider_title" msgid="8870795677024868108">"የእርስዎን <xliff:g id="CREATETYPES">%1$s</xliff:g> የት እንደሚያስቀምጡ ይምረጡ"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"መረጃዎን ለማስቀመጥ እና በቀጣይ ጊዜ በፍጥነት በመለያ ለመግባት የሚስጥር ቁልፍ አስተዳዳሪን ይምረጡ"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"ወደ <xliff:g id="APPNAME">%1$s</xliff:g> ለመግባት የይለፍ ቁልፍ ይፈጠር?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"ወደ <xliff:g id="APPNAME">%1$s</xliff:g> ለመግባት የይለፍ ቃል ይቀመጥ?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"ለ<xliff:g id="APPNAME">%1$s</xliff:g> የመግቢያ መረጃ ይቀመጥ?"</string>
<string name="passkey" msgid="632353688396759522">"የይለፍ ቁልፍ"</string>
<string name="password" msgid="6738570945182936667">"የይለፍ ቃል"</string>
diff --git a/packages/CredentialManager/res/values-ar/strings.xml b/packages/CredentialManager/res/values-ar/strings.xml
index be55469..a2d328c 100644
--- a/packages/CredentialManager/res/values-ar/strings.xml
+++ b/packages/CredentialManager/res/values-ar/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"بينما ننطلق نحو مستقبل بدون كلمات مرور، ستظل كلمات المرور متوفّرة إلى جانب مفاتيح المرور."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"اختيار المكان الذي تريد حفظ <xliff:g id="CREATETYPES">%1$s</xliff:g> فيه"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"اختَر مدير كلمات مرور لحفظ معلوماتك وتسجيل الدخول بشكل أسرع في المرة القادمة."</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"هل تريد إنشاء مفتاح مرور لتسجيل الدخول إلى تطبيق <xliff:g id="APPNAME">%1$s</xliff:g>؟"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"هل تريد حفظ كلمة المرور لتسجيل الدخول إلى تطبيق <xliff:g id="APPNAME">%1$s</xliff:g>؟"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"هل تريد حفظ معلومات تسجيل الدخول لتطبيق \"<xliff:g id="APPNAME">%1$s</xliff:g>\"؟"</string>
<string name="passkey" msgid="632353688396759522">"مفتاح المرور"</string>
<string name="password" msgid="6738570945182936667">"كلمة المرور"</string>
diff --git a/packages/CredentialManager/res/values-as/strings.xml b/packages/CredentialManager/res/values-as/strings.xml
index 6b02ea7..3efcea8 100644
--- a/packages/CredentialManager/res/values-as/strings.xml
+++ b/packages/CredentialManager/res/values-as/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"আমি পাছৱৰ্ডবিহীন ভৱিষ্যতৰ দিশে আগবঢ়াৰ লগে লগে পাছকীৰ লগতে পাছৱৰ্ডসমূহো উপলব্ধ হ’ব।"</string>
<string name="choose_provider_title" msgid="8870795677024868108">"আপোনাৰ <xliff:g id="CREATETYPES">%1$s</xliff:g> ক’ত ছেভ কৰিব লাগে সেয়া বাছনি কৰক"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"আপোনাৰ তথ্য ছেভ কৰি পৰৱৰ্তী সময়ত দ্ৰুতভাৱে ছাইন ইন কৰিবলৈ এটা পাছৱৰ্ড পৰিচালক বাছনি কৰক"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g>ত ছাইন ইন কৰিবলৈ পাছকী সৃষ্টি কৰিবনে?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g>ত ছাইন ইন কৰিবলৈ পাছৱৰ্ড ছেভ কৰিবনে?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g>ৰ বাবে ছাইন ইনৰ তথ্য ছেভ কৰিবনে?"</string>
<string name="passkey" msgid="632353688396759522">"পাছকী"</string>
<string name="password" msgid="6738570945182936667">"পাছৱৰ্ড"</string>
diff --git a/packages/CredentialManager/res/values-az/strings.xml b/packages/CredentialManager/res/values-az/strings.xml
index caef727..627e2c0 100644
--- a/packages/CredentialManager/res/values-az/strings.xml
+++ b/packages/CredentialManager/res/values-az/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Parolsuz gələcəyə doğru irəlilədikcə parollar hələ də açarlar ilə yanaşı əlçatan olacaq."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"<xliff:g id="CREATETYPES">%1$s</xliff:g> elementinin saxlanacağı yeri seçin"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Məlumatlarınızı yadda saxlamaq və növbəti dəfə daha sürətli daxil olmaq üçün parol meneceri seçin"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> tətbiqinə daxil olmaq üçün giriş açarı yaradılsın?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> tətbiqinə daxil olmaq üçün parol yadda saxlansın?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> üçün giriş məlumatları yadda saxlansın?"</string>
<string name="passkey" msgid="632353688396759522">"açar"</string>
<string name="password" msgid="6738570945182936667">"parol"</string>
diff --git a/packages/CredentialManager/res/values-b+sr+Latn/strings.xml b/packages/CredentialManager/res/values-b+sr+Latn/strings.xml
index 0248a08..c4111e4 100644
--- a/packages/CredentialManager/res/values-b+sr+Latn/strings.xml
+++ b/packages/CredentialManager/res/values-b+sr+Latn/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Kako se krećemo ka budućnosti bez lozinki, lozinke će i dalje biti dostupne uz pristupne kodove."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Odaberite gde ćete sačuvati: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Izaberite menadžera lozinki da biste sačuvali podatke i brže se prijavili sledeći put"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Želite da napravite pristupni ključ da biste se prijavili u <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Želite da sačuvate lozinku da biste se prijavili u <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Želite da sačuvate podatke za prijavljivanje za: <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"pristupni kôd"</string>
<string name="password" msgid="6738570945182936667">"lozinka"</string>
diff --git a/packages/CredentialManager/res/values-be/strings.xml b/packages/CredentialManager/res/values-be/strings.xml
index cc841d1..f970d16 100644
--- a/packages/CredentialManager/res/values-be/strings.xml
+++ b/packages/CredentialManager/res/values-be/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Хоць мы ўжо рухаемся ў бок будучыні без выкарыстання пароляў, яны па-ранейшаму застануцца даступнымі нароўні з ключамі доступу."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Выберыце, куды захаваць <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Выберыце менеджар пароляў, каб захаваць свае даныя і забяспечыць хуткі ўваход у наступныя разы"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Стварыць ключ доступу для ўваходу ў праграму \"<xliff:g id="APPNAME">%1$s</xliff:g>\"?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Захаваць пароль для ўваходу ў праграму \"<xliff:g id="APPNAME">%1$s</xliff:g>\"?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Захаваць інфармацыю пра спосаб уваходу ў праграму \"<xliff:g id="APPNAME">%1$s</xliff:g>\"?"</string>
<string name="passkey" msgid="632353688396759522">"ключ доступу"</string>
<string name="password" msgid="6738570945182936667">"пароль"</string>
diff --git a/packages/CredentialManager/res/values-bg/strings.xml b/packages/CredentialManager/res/values-bg/strings.xml
index e7027c1..e3758ea3 100644
--- a/packages/CredentialManager/res/values-bg/strings.xml
+++ b/packages/CredentialManager/res/values-bg/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Паролите ще продължат да са налице заедно с ключовете за достъп по пътя ни към бъдеще без пароли."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Изберете къде да запазите своите <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Изберете мениджър на пароли, в който да се запазят данните ви, така че следващия път да влезете по-бързо в профила си"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Искате ли да създадете ключ за достъп, с който да влизате в(ъв) <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Искате ли да запазите паролата, за да влизате в(ъв) <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Да се запазят ли данните за вход за <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"код за достъп"</string>
<string name="password" msgid="6738570945182936667">"парола"</string>
diff --git a/packages/CredentialManager/res/values-bn/strings.xml b/packages/CredentialManager/res/values-bn/strings.xml
index 49eb68c..b6f9a88 100644
--- a/packages/CredentialManager/res/values-bn/strings.xml
+++ b/packages/CredentialManager/res/values-bn/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"আমরা পাসওয়ার্ডবিহীন ভবিষ্যতের দিকে এগিয়ে গেলেও, এখনও \'পাসকী\'-এর পাশাপাশি পাসওয়ার্ড ব্যবহার করা যাবে।"</string>
<string name="choose_provider_title" msgid="8870795677024868108">"আপনার <xliff:g id="CREATETYPES">%1$s</xliff:g> কোথায় সেভ করবেন তা বেছে নিন"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"আপনার তথ্য সেভ করতে একটি Password Manager বেছে নিন এবং পরের বার আরও দ্রুত সাইন-ইন করুন"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> অ্যাপে সাইন-ইন করার জন্য পাসকী তৈরি করবেন?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> অ্যাপে সাইন-ইন করার জন্য পাসওয়ার্ড সেভ করবেন?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g>-এর জন্য সাইন-ইন সংক্রান্ত তথ্য সেভ করবেন?"</string>
<string name="passkey" msgid="632353688396759522">"পাসকী"</string>
<string name="password" msgid="6738570945182936667">"পাসওয়ার্ড"</string>
diff --git a/packages/CredentialManager/res/values-bs/strings.xml b/packages/CredentialManager/res/values-bs/strings.xml
index afa4882..6c00ac0 100644
--- a/packages/CredentialManager/res/values-bs/strings.xml
+++ b/packages/CredentialManager/res/values-bs/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Kako se krećemo prema budućnosti bez lozinki, lozinke će i dalje biti dostupne uz pristupne ključeve."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Odaberite gdje će se pohranjivati <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Odaberite upravitelja lozinki da sačuvate svoje informacije i brže se prijavite sljedeći put"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Kreirati pristupni ključ da se prijavite u aplikaciju <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Sačuvati lozinku da se prijavite u aplikaciju <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Sačuvati informacije o prijavi za aplikaciju <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"pristupni ključ"</string>
<string name="password" msgid="6738570945182936667">"lozinka"</string>
diff --git a/packages/CredentialManager/res/values-ca/strings.xml b/packages/CredentialManager/res/values-ca/strings.xml
index c937c09..0d0850f 100644
--- a/packages/CredentialManager/res/values-ca/strings.xml
+++ b/packages/CredentialManager/res/values-ca/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Tot i que avancem cap a un futur sense contrasenyes, continuaran estant disponibles juntament amb les claus d\'accés."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Tria on vols desar les <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Selecciona un gestor de contrasenyes per desar la teva informació i iniciar la sessió més ràpidament la pròxima vegada"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Vols crear una clau d\'accés per iniciar la sessió a <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Vols desar la contrasenya per iniciar la sessió a <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Vols desar la informació d\'inici de sessió per a <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"clau d\'accés"</string>
<string name="password" msgid="6738570945182936667">"contrasenya"</string>
diff --git a/packages/CredentialManager/res/values-cs/strings.xml b/packages/CredentialManager/res/values-cs/strings.xml
index 06a81b0..d21afe7 100644
--- a/packages/CredentialManager/res/values-cs/strings.xml
+++ b/packages/CredentialManager/res/values-cs/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Ačkoliv směřujeme k budoucnosti bez hesel, vedle přístupových klíčů budou stále k dispozici i hesla."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Určete, kam ukládat <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Vyberte správce hesel k uložení svých údajů, abyste se příště mohli přihlásit rychleji"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Vytvořit přístupový klíč k přihlašování do aplikace <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Uložit heslo k přihlašování do aplikace <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Uložit přihlašovací údaje pro aplikaci <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"přístupový klíč"</string>
<string name="password" msgid="6738570945182936667">"heslo"</string>
diff --git a/packages/CredentialManager/res/values-da/strings.xml b/packages/CredentialManager/res/values-da/strings.xml
index 207c33c..c868a4b 100644
--- a/packages/CredentialManager/res/values-da/strings.xml
+++ b/packages/CredentialManager/res/values-da/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Selvom vi nærmer os en fremtid, hvor adgangskoder er mindre fremtrædende, kan de stadig bruges i samspil med adgangsnøgler."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Vælg, hvor du vil gemme dine <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Vælg en adgangskodeadministrator for at gemme dine oplysninger, så du kan logge ind hurtigere næste gang"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Vil du oprette en adgangsnøgle for at logge ind på <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Vil du gemme adgangskoden for at logge ind på <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Vil du gemme loginoplysningerne til <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"adgangsnøgle"</string>
<string name="password" msgid="6738570945182936667">"adgangskode"</string>
diff --git a/packages/CredentialManager/res/values-de/strings.xml b/packages/CredentialManager/res/values-de/strings.xml
index 38fa3e9..4fba522 100644
--- a/packages/CredentialManager/res/values-de/strings.xml
+++ b/packages/CredentialManager/res/values-de/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Auch wenn wir uns auf eine passwortlose Zukunft zubewegen, werden neben Passkeys weiter Passwörter verfügbar sein."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Wähle aus, wo deine <xliff:g id="CREATETYPES">%1$s</xliff:g> gespeichert werden sollen"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Du kannst einen Passwortmanager auswählen, um deine Anmeldedaten zu speichern, damit du dich nächstes Mal schneller anmelden kannst"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Passkey zur Anmeldung in <xliff:g id="APPNAME">%1$s</xliff:g> erstellen?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Passwort zur Anmeldung in <xliff:g id="APPNAME">%1$s</xliff:g> speichern?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Anmeldedaten für <xliff:g id="APPNAME">%1$s</xliff:g> speichern?"</string>
<string name="passkey" msgid="632353688396759522">"Passkey"</string>
<string name="password" msgid="6738570945182936667">"Passwort"</string>
diff --git a/packages/CredentialManager/res/values-el/strings.xml b/packages/CredentialManager/res/values-el/strings.xml
index 509cfe6..ad6a424 100644
--- a/packages/CredentialManager/res/values-el/strings.xml
+++ b/packages/CredentialManager/res/values-el/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Καθώς κινούμαστε προς ένα μέλλον χωρίς κωδικούς πρόσβασης, οι κωδικοί πρόσβασης θα εξακολουθούν να είναι διαθέσιμοι μαζί με τα κλειδιά πρόσβασης."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Επιλέξτε πού θα αποθηκεύονται τα <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Επιλέξτε ένα πρόγραμμα διαχείρισης κωδικών πρόσβασης για να αποθηκεύσετε τα στοιχεία σας και να συνδεθείτε πιο γρήγορα την επόμενη φορά."</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Δημιουργία κλειδιού πρόσβασης για σύνδεση στην εφαρμογή <xliff:g id="APPNAME">%1$s</xliff:g>;"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Αποθήκευση κωδικού πρόσβασης για σύνδεση στην εφαρμογή <xliff:g id="APPNAME">%1$s</xliff:g>;"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Αποθήκευση στοιχείων σύνδεσης για <xliff:g id="APPNAME">%1$s</xliff:g>;"</string>
<string name="passkey" msgid="632353688396759522">"κλειδί πρόσβασης"</string>
<string name="password" msgid="6738570945182936667">"κωδικός πρόσβασης"</string>
diff --git a/packages/CredentialManager/res/values-en-rAU/strings.xml b/packages/CredentialManager/res/values-en-rAU/strings.xml
index cd63b41..6aa1b5e 100644
--- a/packages/CredentialManager/res/values-en-rAU/strings.xml
+++ b/packages/CredentialManager/res/values-en-rAU/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"As we move towards a passwordless future, passwords will still be available alongside passkeys."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Choose where to save your <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Select a password manager to save your info and sign in faster next time"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Create passkey to sign in to <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Save password to sign in to <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Save sign-in info for <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"passkey"</string>
<string name="password" msgid="6738570945182936667">"password"</string>
diff --git a/packages/CredentialManager/res/values-en-rCA/strings.xml b/packages/CredentialManager/res/values-en-rCA/strings.xml
index ff1de20..10d95993 100644
--- a/packages/CredentialManager/res/values-en-rCA/strings.xml
+++ b/packages/CredentialManager/res/values-en-rCA/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"As we move towards a passwordless future, passwords will still be available alongside passkeys."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Choose where to save your <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Select a password manager to save your info and sign in faster next time"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Create passkey to sign in to <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Save password to sign in to <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Save sign-in info for <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"passkey"</string>
<string name="password" msgid="6738570945182936667">"password"</string>
diff --git a/packages/CredentialManager/res/values-en-rGB/strings.xml b/packages/CredentialManager/res/values-en-rGB/strings.xml
index cd63b41..6aa1b5e 100644
--- a/packages/CredentialManager/res/values-en-rGB/strings.xml
+++ b/packages/CredentialManager/res/values-en-rGB/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"As we move towards a passwordless future, passwords will still be available alongside passkeys."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Choose where to save your <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Select a password manager to save your info and sign in faster next time"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Create passkey to sign in to <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Save password to sign in to <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Save sign-in info for <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"passkey"</string>
<string name="password" msgid="6738570945182936667">"password"</string>
diff --git a/packages/CredentialManager/res/values-en-rIN/strings.xml b/packages/CredentialManager/res/values-en-rIN/strings.xml
index cd63b41..6aa1b5e 100644
--- a/packages/CredentialManager/res/values-en-rIN/strings.xml
+++ b/packages/CredentialManager/res/values-en-rIN/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"As we move towards a passwordless future, passwords will still be available alongside passkeys."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Choose where to save your <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Select a password manager to save your info and sign in faster next time"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Create passkey to sign in to <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Save password to sign in to <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Save sign-in info for <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"passkey"</string>
<string name="password" msgid="6738570945182936667">"password"</string>
diff --git a/packages/CredentialManager/res/values-en-rXC/strings.xml b/packages/CredentialManager/res/values-en-rXC/strings.xml
index 8ffe001..16c0d1d 100644
--- a/packages/CredentialManager/res/values-en-rXC/strings.xml
+++ b/packages/CredentialManager/res/values-en-rXC/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"As we move towards a passwordless future, passwords will still be available alongside passkeys."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Choose where to save your <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Select a password manager to save your info and sign in faster next time"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Create passkey to sign in to <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Save password to sign in to <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Save sign-in info for <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"passkey"</string>
<string name="password" msgid="6738570945182936667">"password"</string>
diff --git a/packages/CredentialManager/res/values-es-rUS/strings.xml b/packages/CredentialManager/res/values-es-rUS/strings.xml
index 29fb64d..6bab1ae 100644
--- a/packages/CredentialManager/res/values-es-rUS/strings.xml
+++ b/packages/CredentialManager/res/values-es-rUS/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"A medida que avanzamos hacia un futuro sin contraseñas, estas seguirán estando disponibles junto a las llaves de acceso."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Elige dónde guardar tus <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Selecciona un administrador de contraseñas para guardar tu información y acceder más rápido la próxima vez"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"¿Quieres crear una llave de acceso para acceder a <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"¿Quieres guardar la contraseña para acceder a <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"¿Quieres guardar la información de acceso para <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"llave de acceso"</string>
<string name="password" msgid="6738570945182936667">"contraseña"</string>
diff --git a/packages/CredentialManager/res/values-es/strings.xml b/packages/CredentialManager/res/values-es/strings.xml
index 077ea99..a727f458 100644
--- a/packages/CredentialManager/res/values-es/strings.xml
+++ b/packages/CredentialManager/res/values-es/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Aunque nos dirigimos hacia un mundo sin contraseñas, estas seguirán estando disponibles junto con las llaves de acceso."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Elige dónde guardar tus <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Selecciona un gestor de contraseñas para guardar tu información e iniciar sesión más rápido la próxima vez"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"¿Crear llave de acceso para iniciar sesión en <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"¿Guardar contraseña para iniciar sesión en <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"¿Guardar la información de inicio de sesión de <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"llave de acceso"</string>
<string name="password" msgid="6738570945182936667">"contraseña"</string>
diff --git a/packages/CredentialManager/res/values-et/strings.xml b/packages/CredentialManager/res/values-et/strings.xml
index 6de564b..2727612 100644
--- a/packages/CredentialManager/res/values-et/strings.xml
+++ b/packages/CredentialManager/res/values-et/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Liikudes paroolivaba tuleviku poole, jäävad paroolid pääsuvõtmete kõrval siiski kättesaadavaks."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Valige, kuhu soovite oma <xliff:g id="CREATETYPES">%1$s</xliff:g> salvestada"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Valige paroolihaldur, et salvestada oma teave ja järgmisel korral kiiremini sisse logida"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Kas luua rakendusse <xliff:g id="APPNAME">%1$s</xliff:g> sisselogimiseks pääsuvõti?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Kas salvestada rakendusse <xliff:g id="APPNAME">%1$s</xliff:g> sisselogimiseks parool?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Kas salvestada rakenduse <xliff:g id="APPNAME">%1$s</xliff:g> jaoks sisselogimisandmed?"</string>
<string name="passkey" msgid="632353688396759522">"pääsuvõti"</string>
<string name="password" msgid="6738570945182936667">"parool"</string>
diff --git a/packages/CredentialManager/res/values-eu/strings.xml b/packages/CredentialManager/res/values-eu/strings.xml
index 018968c..f0debcc 100644
--- a/packages/CredentialManager/res/values-eu/strings.xml
+++ b/packages/CredentialManager/res/values-eu/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Pasahitzik gabeko etorkizun baterantz goazen arren, pasahitzek sarbide-gakoen bizikide izaten jarraituko dute."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Aukeratu non gorde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Hautatu informazioa gordetzeko pasahitz-kudeatzaile bat eta hasi saioa bizkorrago hurrengoan"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> aplikazioan saioa hasteko sarbide-gako bat sortu nahi duzu?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> aplikazioan saioa hasteko pasahitza gorde nahi duzu?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> aplikazioko saioa hasteko informazioa gorde nahi duzu?"</string>
<string name="passkey" msgid="632353688396759522">"sarbide-gakoa"</string>
<string name="password" msgid="6738570945182936667">"pasahitza"</string>
diff --git a/packages/CredentialManager/res/values-fa/strings.xml b/packages/CredentialManager/res/values-fa/strings.xml
index 55a79d8..a88b353 100644
--- a/packages/CredentialManager/res/values-fa/strings.xml
+++ b/packages/CredentialManager/res/values-fa/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"درحالیکه بهسوی آیندهای بیگذرواژه حرکت میکنیم، گذرواژهها همچنان در کنار گذرکلیدها دردسترس خواهند بود"</string>
<string name="choose_provider_title" msgid="8870795677024868108">"جایی را برای ذخیره کردن <xliff:g id="CREATETYPES">%1$s</xliff:g> انتخاب کنید"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"مدیر گذرواژهای انتخاب کنید تا اطلاعاتتان را ذخیره کنید و دفعه بعد سریعتر به سیستم وارد شوید"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"برای ورود به سیستم <xliff:g id="APPNAME">%1$s</xliff:g>، گذرکلید ایجاد شود؟"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"برای ورود به سیستم <xliff:g id="APPNAME">%1$s</xliff:g>، گذرواژه ذخیره شود؟"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"اطلاعات ورود به سیستم <xliff:g id="APPNAME">%1$s</xliff:g> ذخیره شود؟"</string>
<string name="passkey" msgid="632353688396759522">"گذرکلید"</string>
<string name="password" msgid="6738570945182936667">"گذرواژه"</string>
diff --git a/packages/CredentialManager/res/values-fi/strings.xml b/packages/CredentialManager/res/values-fi/strings.xml
index 9f95720..82af70f 100644
--- a/packages/CredentialManager/res/values-fi/strings.xml
+++ b/packages/CredentialManager/res/values-fi/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Kehitys kulkee kohti salasanatonta tulevaisuutta, mutta salasanat ovat edelleen käytettävissä avainkoodien ohella."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Valitse, minne <xliff:g id="CREATETYPES">%1$s</xliff:g> tallennetaan"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Valitse salasanojen ylläpitotyökalu, niin voit tallentaa tietosi ja kirjautua ensi kerralla nopeammin sisään"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Luodaanko avainkoodi sisäänkirjautumista (<xliff:g id="APPNAME">%1$s</xliff:g>) varten?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Tallennetaanko salasana sisäänkirjautumista (<xliff:g id="APPNAME">%1$s</xliff:g>) varten?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Tallennetaanko kirjautumistiedot (<xliff:g id="APPNAME">%1$s</xliff:g>)?"</string>
<string name="passkey" msgid="632353688396759522">"avainkoodi"</string>
<string name="password" msgid="6738570945182936667">"salasana"</string>
diff --git a/packages/CredentialManager/res/values-fr-rCA/strings.xml b/packages/CredentialManager/res/values-fr-rCA/strings.xml
index 481fac9..61f2204 100644
--- a/packages/CredentialManager/res/values-fr-rCA/strings.xml
+++ b/packages/CredentialManager/res/values-fr-rCA/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"À mesure que nous nous dirigeons vers un avenir sans mot de passe, ils resteront toujours utilisés parallèlement aux clés d\'accès."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Choisir où enregistrer vos <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Sélectionnez un gestionnaire de mots de passe pour enregistrer vos renseignements et vous connecter plus rapidement la prochaine fois"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Créer une clé d\'accès pour se connecter à <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Enregistrer un mot de passe pour se connecter à <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Enregistrer les renseignements de connexion pour <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"clé d\'accès"</string>
<string name="password" msgid="6738570945182936667">"mot de passe"</string>
diff --git a/packages/CredentialManager/res/values-fr/strings.xml b/packages/CredentialManager/res/values-fr/strings.xml
index 37df7fb..15715f3 100644
--- a/packages/CredentialManager/res/values-fr/strings.xml
+++ b/packages/CredentialManager/res/values-fr/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Nous nous dirigeons vers un futur sans mots de passe, mais ceux-ci resteront disponibles en plus des clés d\'accès."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Choisissez où enregistrer vos <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Sélectionnez un gestionnaire de mots de passe pour enregistrer vos informations et vous connecter plus rapidement la prochaine fois"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Créer une clé d\'accès pour se connecter à <xliff:g id="APPNAME">%1$s</xliff:g> ?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Enregistrer un mot de passe pour se connecter à <xliff:g id="APPNAME">%1$s</xliff:g> ?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Enregistrer les informations de connexion pour <xliff:g id="APPNAME">%1$s</xliff:g> ?"</string>
<string name="passkey" msgid="632353688396759522">"clé d\'accès"</string>
<string name="password" msgid="6738570945182936667">"mot de passe"</string>
diff --git a/packages/CredentialManager/res/values-gl/strings.xml b/packages/CredentialManager/res/values-gl/strings.xml
index 8770465..1ca0774 100644
--- a/packages/CredentialManager/res/values-gl/strings.xml
+++ b/packages/CredentialManager/res/values-gl/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Durante este percorrido cara a un futuro sen contrasinais, estes seguirán estando dispoñibles a canda as claves de acceso."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Escolle onde queres gardar: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Selecciona un xestor de contrasinais para gardar a túa información e iniciar sesión máis rápido a próxima vez"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Queres crear unha clave de acceso para iniciar sesión en <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Queres gardar o contrasinal para iniciar sesión en <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Queres gardar a información de inicio de sesión de <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"clave de acceso"</string>
<string name="password" msgid="6738570945182936667">"contrasinal"</string>
diff --git a/packages/CredentialManager/res/values-gu/strings.xml b/packages/CredentialManager/res/values-gu/strings.xml
index efc88c1..7d8df9a 100644
--- a/packages/CredentialManager/res/values-gu/strings.xml
+++ b/packages/CredentialManager/res/values-gu/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"આપણે પાસવર્ડ રહિત ભવિષ્ય તરફ આગળ વધી રહ્યાં છીએ, છતાં પાસકીની સાથોસાથ હજી પણ પાસવર્ડ ઉપલબ્ધ રહેશે."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"તમારી <xliff:g id="CREATETYPES">%1$s</xliff:g> ક્યાં સાચવવી તે પસંદ કરો"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"તમારી માહિતી સાચવવા માટે પાસવર્ડ મેનેજર પસંદ કરો અને આગલી વખતે વધુ ઝડપથી સાઇન ઇન કરો"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g>માં સાઇન ઇન કરવા માટે પાસકી બનાવીએ?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g>માં પાસવર્ડ સાચવવા માટે પાસકી બનાવીએ?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> માટે સાઇન-ઇન કરવાની માહિતી સાચવીએ?"</string>
<string name="passkey" msgid="632353688396759522">"પાસકી"</string>
<string name="password" msgid="6738570945182936667">"પાસવર્ડ"</string>
diff --git a/packages/CredentialManager/res/values-hi/strings.xml b/packages/CredentialManager/res/values-hi/strings.xml
index 661a4a2..b644864 100644
--- a/packages/CredentialManager/res/values-hi/strings.xml
+++ b/packages/CredentialManager/res/values-hi/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"आने वाले समय में बिना पासवर्ड वाली टेक्नोलॉजी यानी पासकी का इस्तेमाल बढ़ेगा, हालांकि इसके साथ-साथ पासवर्ड भी इस्तेमाल किए जा सकेंगे."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"चुनें कि अपनी <xliff:g id="CREATETYPES">%1$s</xliff:g> कहां सेव करनी हैं"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"अपनी जानकारी सेव करने के लिए, पासवर्ड मैनेजर चुनें और अगली बार ज़्यादा तेज़ी से साइन इन करें"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"क्या आपको <xliff:g id="APPNAME">%1$s</xliff:g> में साइन इन करने के लिए पासकी बनानी है?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"क्या आपको <xliff:g id="APPNAME">%1$s</xliff:g> में साइन इन करने के लिए पासवर्ड सेव करना है?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"क्या आपको <xliff:g id="APPNAME">%1$s</xliff:g> के लिए साइन-इन की जानकारी सेव करनी है?"</string>
<string name="passkey" msgid="632353688396759522">"पासकी"</string>
<string name="password" msgid="6738570945182936667">"पासवर्ड"</string>
diff --git a/packages/CredentialManager/res/values-hr/strings.xml b/packages/CredentialManager/res/values-hr/strings.xml
index eceb1b5..86d52d5 100644
--- a/packages/CredentialManager/res/values-hr/strings.xml
+++ b/packages/CredentialManager/res/values-hr/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Kako idemo u smjeru budućnosti bez zaporki, one će i dalje biti dostupne uz pristupne ključeve."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Odaberite gdje će se spremati <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Odaberite upravitelja zaporki kako biste spremili svoje informacije i drugi se put brže prijavili"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Želite li izraditi pristupni ključ za prijavu u aplikaciju <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Želite li spremiti zaporku za prijavu u aplikaciju <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Spremiti informacije o prijavi za <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"pristupni ključ"</string>
<string name="password" msgid="6738570945182936667">"zaporka"</string>
diff --git a/packages/CredentialManager/res/values-hu/strings.xml b/packages/CredentialManager/res/values-hu/strings.xml
index 3415eea..539feb4 100644
--- a/packages/CredentialManager/res/values-hu/strings.xml
+++ b/packages/CredentialManager/res/values-hu/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Miközben a jelszó nélküli jövő felé haladunk, a jelszavak továbbra is rendelkezésre állnak majd az azonosítókulcsok mellett."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Válassza ki, hogy hova szeretné menteni <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Válasszon jelszókezelőt, hogy menthesse az adatait, és gyorsabban jelentkezhessen be a következő alkalommal."</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Létrehoz azonosítókulcsot a következőbe való bejelentkezéshez: <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Menti a jelszót a következőbe való bejelentkezéshez: <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Menti a bejelentkezési adatokat a következőhöz: <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"azonosítókulcs"</string>
<string name="password" msgid="6738570945182936667">"jelszó"</string>
diff --git a/packages/CredentialManager/res/values-hy/strings.xml b/packages/CredentialManager/res/values-hy/strings.xml
index af803b4..5f06a7a 100644
--- a/packages/CredentialManager/res/values-hy/strings.xml
+++ b/packages/CredentialManager/res/values-hy/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Թեև մենք առանց գաղտնաբառերի ապագայի ճանապարհին ենք, դրանք դեռ հասանելի կլինեն անցաբառերի հետ մեկտեղ։"</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Նշեք, թե որտեղ եք ուզում պահել ձեր <xliff:g id="CREATETYPES">%1$s</xliff:g>ը"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Ընտրեք գաղտնաբառերի կառավարիչ՝ ձեր տեղեկությունները պահելու և հաջորդ անգամ ավելի արագ մուտք գործելու համար"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Ստեղծե՞լ անցաբառ՝ <xliff:g id="APPNAME">%1$s</xliff:g> հավելված մուտք գործելու համար"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Պահե՞լ գաղտնաբառը՝ <xliff:g id="APPNAME">%1$s</xliff:g> հավելված մուտք գործելու համար"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Պահե՞լ «<xliff:g id="APPNAME">%1$s</xliff:g>» հավելվածի մուտքի տվյալները"</string>
<string name="passkey" msgid="632353688396759522">"անցաբառ"</string>
<string name="password" msgid="6738570945182936667">"գաղտնաբառ"</string>
diff --git a/packages/CredentialManager/res/values-in/strings.xml b/packages/CredentialManager/res/values-in/strings.xml
index 180c869..ad8aeec 100644
--- a/packages/CredentialManager/res/values-in/strings.xml
+++ b/packages/CredentialManager/res/values-in/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Sandi akan tetap tersedia bersama kunci sandi seiring perjalanan menuju era di mana sandi tidak diperlukan lagi."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Pilih tempat penyimpanan <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Pilih pengelola sandi untuk menyimpan info Anda dan login lebih cepat lain kali"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Buat kunci sandi untuk login ke <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Simpan sandi untuk login ke <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Simpan info login untuk <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"kunci sandi"</string>
<string name="password" msgid="6738570945182936667">"sandi"</string>
diff --git a/packages/CredentialManager/res/values-is/strings.xml b/packages/CredentialManager/res/values-is/strings.xml
index 332d15e..bc6bdfc 100644
--- a/packages/CredentialManager/res/values-is/strings.xml
+++ b/packages/CredentialManager/res/values-is/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Við stefnum að framtíð án aðgangsorða en aðgangsorð verða áfram í boði samhliða aðgangslyklum."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Veldu hvar þú vilt vista <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Veldu aðgangsorðastjórnun til að vista upplýsingarnar og vera fljótari að skrá þig inn næst"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Búa til aðgangslykil til að skrá þig inn á <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Vista aðgangsorð til að skrá þig inn á <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Viltu vista innskráningarupplýsingar fyrir <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"aðgangslykill"</string>
<string name="password" msgid="6738570945182936667">"aðgangsorð"</string>
diff --git a/packages/CredentialManager/res/values-it/strings.xml b/packages/CredentialManager/res/values-it/strings.xml
index 5c83336..2b0d83c 100644
--- a/packages/CredentialManager/res/values-it/strings.xml
+++ b/packages/CredentialManager/res/values-it/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Il futuro sarà senza password, ma per ora saranno ancora disponibili insieme alle passkey."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Scegli dove salvare le <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Seleziona un gestore delle password per salvare i tuoi dati e accedere più velocemente la prossima volta"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Creare passkey per accedere all\'app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Salvare password per accedere all\'app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Vuoi salvare i dati di accesso di <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"passkey"</string>
<string name="password" msgid="6738570945182936667">"password"</string>
diff --git a/packages/CredentialManager/res/values-iw/strings.xml b/packages/CredentialManager/res/values-iw/strings.xml
index 55fb9f2..ee9dd5d 100644
--- a/packages/CredentialManager/res/values-iw/strings.xml
+++ b/packages/CredentialManager/res/values-iw/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"אנחנו מתקדמים לעבר עתיד ללא סיסמאות, אבל עדיין אפשר יהיה להשתמש בסיסמאות וגם במפתחות גישה."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"בחירת המקום לשמירה של <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"אפשר לבחור באחד משירותי ניהול הסיסמאות כדי לשמור את הפרטים ולהיכנס לחשבון מהר יותר בפעם הבאה"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"ליצור מפתח גישה כדי להיכנס לחשבון ב-<xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"לשמור את הסיסמה כדי להיכנס לחשבון ב-<xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"לשמור את פרטי הכניסה של <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"מפתח גישה"</string>
<string name="password" msgid="6738570945182936667">"סיסמה"</string>
diff --git a/packages/CredentialManager/res/values-ja/strings.xml b/packages/CredentialManager/res/values-ja/strings.xml
index 1ffc7db..b60638b 100644
--- a/packages/CredentialManager/res/values-ja/strings.xml
+++ b/packages/CredentialManager/res/values-ja/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"将来的にパスワードレスに移行するにあたり、パスワードもパスキーと並行して引き続きご利用いただけます。"</string>
<string name="choose_provider_title" msgid="8870795677024868108">"<xliff:g id="CREATETYPES">%1$s</xliff:g>の保存先を選択"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"パスワード マネージャーを選択して情報を保存しておくと、次回からすばやくログインできます"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> にログインするためにパスキーを作成しますか?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> にログインするためにパスワードを保存しますか?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> のログイン情報を保存しますか?"</string>
<string name="passkey" msgid="632353688396759522">"パスキー"</string>
<string name="password" msgid="6738570945182936667">"パスワード"</string>
diff --git a/packages/CredentialManager/res/values-ka/strings.xml b/packages/CredentialManager/res/values-ka/strings.xml
index fdf0797..c089f4a 100644
--- a/packages/CredentialManager/res/values-ka/strings.xml
+++ b/packages/CredentialManager/res/values-ka/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"უპაროლო მომავალში პაროლები კვლავ ხელმისაწვდომი იქნება, წვდომის გასაღებებთან ერთად."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"აირჩიეთ სად შეინახოთ თქვენი <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"აირჩიეთ პაროლების მმართველი თქვენი ინფორმაციის შესანახად, რომ მომავალში უფრო სწრაფად შეხვიდეთ."</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"შესასვლელად წვდომის გასაღების შექმნა: <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"შესასვლელი პაროლის შენახვა: <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"შეინახავთ <xliff:g id="APPNAME">%1$s</xliff:g> აპში შესვლის ინფორმაციას?"</string>
<string name="passkey" msgid="632353688396759522">"წვდომის გასაღები"</string>
<string name="password" msgid="6738570945182936667">"პაროლი"</string>
diff --git a/packages/CredentialManager/res/values-kk/strings.xml b/packages/CredentialManager/res/values-kk/strings.xml
index 1c1b186..2200b18 100644
--- a/packages/CredentialManager/res/values-kk/strings.xml
+++ b/packages/CredentialManager/res/values-kk/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Құпия сөзсіз болашақ жақын болғанына қарамастан, келешекте құпия сөздерді кіру кілттерімен қатар қолдана беруге болады."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"<xliff:g id="CREATETYPES">%1$s</xliff:g> қайда сақталатынын таңдаңыз"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Мәліметіңізді сақтап, келесіде жылдам кіру үшін құпия сөз менеджерін таңдаңыз."</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> қолданбасына кіру үшін кіру кілті жасалсын ба?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> қолданбасына кіру үшін құпия сөз сақталсын ба?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> үшін кіру мәліметін сақтау керек пе?"</string>
<string name="passkey" msgid="632353688396759522">"Кіру кілті"</string>
<string name="password" msgid="6738570945182936667">"құпия сөз"</string>
diff --git a/packages/CredentialManager/res/values-km/strings.xml b/packages/CredentialManager/res/values-km/strings.xml
index 0840879..d0faaec 100644
--- a/packages/CredentialManager/res/values-km/strings.xml
+++ b/packages/CredentialManager/res/values-km/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"នៅពេលដែលយើងឈានទៅរកអនាគតដែលគ្មានពាក្យសម្ងាត់ ពាក្យសម្ងាត់នៅតែអាចប្រើបានរួមជាមួយកូដសម្ងាត់។"</string>
<string name="choose_provider_title" msgid="8870795677024868108">"ជ្រើសរើសកន្លែងដែលត្រូវរក្សាទុក<xliff:g id="CREATETYPES">%1$s</xliff:g>របស់អ្នក"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"ជ្រើសរើសកម្មវិធីគ្រប់គ្រងពាក្យសម្ងាត់ ដើម្បីរក្សាទុកព័ត៌មានរបស់អ្នក និងចូលគណនីបានកាន់តែរហ័សនៅពេលលើកក្រោយ"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"បង្កើតកូដសម្ងាត់ ដើម្បីចូលគណនី <xliff:g id="APPNAME">%1$s</xliff:g> ឬ?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"រក្សាទុកពាក្យសម្ងាត់ ដើម្បីចូលគណនី <xliff:g id="APPNAME">%1$s</xliff:g> ឬ?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"រក្សាទុកព័ត៌មានអំពីការចូលគណនីសម្រាប់ <xliff:g id="APPNAME">%1$s</xliff:g> ឬ?"</string>
<string name="passkey" msgid="632353688396759522">"កូដសម្ងាត់"</string>
<string name="password" msgid="6738570945182936667">"ពាក្យសម្ងាត់"</string>
diff --git a/packages/CredentialManager/res/values-kn/strings.xml b/packages/CredentialManager/res/values-kn/strings.xml
index 84549d1..59e3b5c 100644
--- a/packages/CredentialManager/res/values-kn/strings.xml
+++ b/packages/CredentialManager/res/values-kn/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"ನಾವು ಪಾಸ್ವರ್ಡ್ ರಹಿತ ತಂತ್ರಜ್ಞಾನದ ಕಡೆಗೆ ಸಾಗುತ್ತಿರುವಾಗ, ಪಾಸ್ಕೀಗಳ ಜೊತೆಗೆ ಪಾಸ್ವರ್ಡ್ಗಳು ಇನ್ನೂ ಲಭ್ಯವಿರುತ್ತವೆ."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"ನಿಮ್ಮ <xliff:g id="CREATETYPES">%1$s</xliff:g> ಎಲ್ಲಿ ಸೇವ್ ಆಗಬೇಕು ಎಂಬುದನ್ನು ಆರಿಸಿ"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"ನಿಮ್ಮ ಮಾಹಿತಿಯನ್ನು ಉಳಿಸಲು ಪಾಸ್ವರ್ಡ್ ನಿರ್ವಾಹಕವನ್ನು ಆಯ್ಕೆಮಾಡಿ ಹಾಗೂ ಮುಂದಿನ ಬಾರಿ ವೇಗವಾಗಿ ಸೈನ್ ಇನ್ ಮಾಡಿ"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> ಗೆ ಸೈನ್ ಇನ್ ಮಾಡಲು ಪಾಸ್ಕೀ ರಚಿಸುವುದೇ?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> ಗೆ ಸೈನ್ ಇನ್ ಮಾಡಲು ಪಾಸ್ವರ್ಡ್ ಅನ್ನು ಸೇವ್ ಮಾಡುವುದೇ?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> ಗಾಗಿ ಸೈನ್-ಇನ್ ಮಾಹಿತಿಯನ್ನು ಉಳಿಸುವುದೇ?"</string>
<string name="passkey" msgid="632353688396759522">"ಪಾಸ್ಕೀ"</string>
<string name="password" msgid="6738570945182936667">"ಪಾಸ್ವರ್ಡ್"</string>
diff --git a/packages/CredentialManager/res/values-ko/strings.xml b/packages/CredentialManager/res/values-ko/strings.xml
index 0c970dd..fd48d18 100644
--- a/packages/CredentialManager/res/values-ko/strings.xml
+++ b/packages/CredentialManager/res/values-ko/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"비밀번호 없는 미래로 나아가는 과정에서 비밀번호는 여전히 패스키와 함께 사용될 것입니다."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"<xliff:g id="CREATETYPES">%1$s</xliff:g> 저장 위치 선택"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"정보를 저장해서 다음에 더 빠르게 로그인하려면 비밀번호 관리자를 선택하세요."</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"패스키를 생성하여 <xliff:g id="APPNAME">%1$s</xliff:g>에 로그인하시겠습니까?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"비밀번호를 저장하여 <xliff:g id="APPNAME">%1$s</xliff:g>에 로그인하시겠습니까?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g>의 로그인 정보를 저장하시겠습니까?"</string>
<string name="passkey" msgid="632353688396759522">"패스키"</string>
<string name="password" msgid="6738570945182936667">"비밀번호"</string>
diff --git a/packages/CredentialManager/res/values-ky/strings.xml b/packages/CredentialManager/res/values-ky/strings.xml
index 3937ff5..6a01462 100644
--- a/packages/CredentialManager/res/values-ky/strings.xml
+++ b/packages/CredentialManager/res/values-ky/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Сырсөзсүз келечекти көздөй баратсак да, аларды киргизүүчү ачкычтар менен бирге колдоно берүүгө болот."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"<xliff:g id="CREATETYPES">%1$s</xliff:g> кайда сакталарын тандаңыз"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Маалыматыңызды сактоо жана кийинки жолу тезирээк кирүү үчүн сырсөздөрдү башкаргычты тандаңыз"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> колдонмосуна кирүү үчүн киргизүүчү ачкычты түзөсүзбү?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> колдонмосуна кирүү үчүн сырсөздү сактайсызбы?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> үчүн кирүү маалыматы сакталсынбы?"</string>
<string name="passkey" msgid="632353688396759522">"киргизүүчү ачкыч"</string>
<string name="password" msgid="6738570945182936667">"сырсөз"</string>
diff --git a/packages/CredentialManager/res/values-lo/strings.xml b/packages/CredentialManager/res/values-lo/strings.xml
index 79a31e8..e71c60f 100644
--- a/packages/CredentialManager/res/values-lo/strings.xml
+++ b/packages/CredentialManager/res/values-lo/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"ໃນຂະນະທີ່ພວກເຮົາກ້າວໄປສູ່ອະນາຄົດທີ່ບໍ່ຕ້ອງໃຊ້ລະຫັດຜ່ານ, ລະຫັດຜ່ານຈະຍັງຄົງໃຊ້ໄດ້ຄວບຄູ່ໄປກັບກະແຈຜ່ານ."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"ເລືອກບ່ອນທີ່ຈະບັນທຶກ <xliff:g id="CREATETYPES">%1$s</xliff:g> ຂອງທ່ານ"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"ເລືອກຕົວຈັດການລະຫັດຜ່ານເພື່ອບັນທຶກຂໍ້ມູນຂອງທ່ານ ແລະ ເຂົ້າສູ່ລະບົບໄວຂຶ້ນໃນເທື່ອຕໍ່ໄປ"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"ສ້າງກະແຈຜ່ານເພື່ອເຂົ້າສູ່ລະບົບ <xliff:g id="APPNAME">%1$s</xliff:g> ບໍ?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"ບັນທຶກລະຫັດຜ່ານເພື່ອເຂົ້າສູ່ລະບົບ <xliff:g id="APPNAME">%1$s</xliff:g> ບໍ?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"ບັນທຶກຂໍ້ມູນການເຂົ້າສູ່ລະບົບສຳລັບ <xliff:g id="APPNAME">%1$s</xliff:g> ບໍ?"</string>
<string name="passkey" msgid="632353688396759522">"ກະແຈຜ່ານ"</string>
<string name="password" msgid="6738570945182936667">"ລະຫັດຜ່ານ"</string>
diff --git a/packages/CredentialManager/res/values-lt/strings.xml b/packages/CredentialManager/res/values-lt/strings.xml
index e6bcd06..55c5cd2 100644
--- a/packages/CredentialManager/res/values-lt/strings.xml
+++ b/packages/CredentialManager/res/values-lt/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Kol stengiamės padaryti, kad ateityje nereikėtų naudoti slaptažodžių, jie vis dar bus pasiekiami kartu su prieigos raktais."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Pasirinkite, kur išsaugoti „<xliff:g id="CREATETYPES">%1$s</xliff:g>“"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Pasirinkite slaptažodžių tvarkyklę, kurią naudodami galėsite išsaugoti informaciją ir kitą kartą prisijungti greičiau"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Sukurti prieigos raktą, skirtą prisijungti prie „<xliff:g id="APPNAME">%1$s</xliff:g>“?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Sukurti slaptažodį, skirtą prisijungti prie „<xliff:g id="APPNAME">%1$s</xliff:g>“?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Išsaugoti prisijungimo prie „<xliff:g id="APPNAME">%1$s</xliff:g>“ informaciją?"</string>
<string name="passkey" msgid="632353688396759522">"„passkey“"</string>
<string name="password" msgid="6738570945182936667">"slaptažodis"</string>
diff --git a/packages/CredentialManager/res/values-lv/strings.xml b/packages/CredentialManager/res/values-lv/strings.xml
index a62bf28..2c0f8e1 100644
--- a/packages/CredentialManager/res/values-lv/strings.xml
+++ b/packages/CredentialManager/res/values-lv/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Lai arī pamazām notiek pāreja uz darbu bez parolēm, tās joprojām būs pieejamas līdzās piekļuves atslēgām."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Izvēlieties, kur saglabāt savas <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Lai saglabātu informāciju un nākamreiz varētu pierakstīties ātrāk, atlasiet paroļu pārvaldnieku."</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Vai izveidot piekļuves atslēgu, lai pierakstītos lietotnē <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Vai saglabāt paroli, lai pierakstītos lietotnē <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Vai saglabāt pierakstīšanās informāciju lietotnei <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"piekļuves atslēga"</string>
<string name="password" msgid="6738570945182936667">"parole"</string>
diff --git a/packages/CredentialManager/res/values-mk/strings.xml b/packages/CredentialManager/res/values-mk/strings.xml
index b5d5996..6f8f76b 100644
--- a/packages/CredentialManager/res/values-mk/strings.xml
+++ b/packages/CredentialManager/res/values-mk/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Како што се движиме кон иднина без лозинки, лозинките сепак ќе бидат достапни покрај криптографските клучеви."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Изберете каде да ги зачувате вашите <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Изберете управник со лозинки за да ги зачувате вашите податоци и да се најавите побрзо следниот пат"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Да се создаде криптографски клуч за најавување на <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Да се зачува лозинката за најавување на <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Да се зачуваат податоците за најавување за <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"криптографски клуч"</string>
<string name="password" msgid="6738570945182936667">"лозинка"</string>
diff --git a/packages/CredentialManager/res/values-ml/strings.xml b/packages/CredentialManager/res/values-ml/strings.xml
index fcbd12b..ab32bc9 100644
--- a/packages/CredentialManager/res/values-ml/strings.xml
+++ b/packages/CredentialManager/res/values-ml/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"നമ്മൾ പാസ്വേഡ് രഹിത ഭാവിയിലേക്ക് ചുവടുവെച്ചുകൊണ്ടിരിക്കുകയാണ് എങ്കിലും, പാസ്കീകൾക്കൊപ്പം പാസ്വേഡുകൾ തുടർന്നും ലഭ്യമായിരിക്കും."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"നിങ്ങളുടെ <xliff:g id="CREATETYPES">%1$s</xliff:g> എവിടെയാണ് സംരക്ഷിക്കേണ്ടതെന്ന് തിരഞ്ഞെടുക്കുക"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"നിങ്ങളുടെ വിവരങ്ങൾ സംരക്ഷിക്കാനും അടുത്ത തവണ വേഗത്തിൽ സൈൻ ഇൻ ചെയ്യാനും ഒരു പാസ്വേഡ് മാനേജർ തിരഞ്ഞെടുക്കുക"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> എന്നതിലേക്ക് സൈൻ ഇൻ ചെയ്യാൻ പാസ്കീ സൃഷ്ടിക്കണോ?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> എന്നതിലേക്ക് സൈൻ ഇൻ ചെയ്യാൻ പാസ്വേഡ് സംരക്ഷിക്കണോ?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> എന്നതിനായി സൈൻ ഇൻ വിവരങ്ങൾ സംരക്ഷിക്കണോ?"</string>
<string name="passkey" msgid="632353688396759522">"പാസ്കീ"</string>
<string name="password" msgid="6738570945182936667">"പാസ്വേഡ്"</string>
diff --git a/packages/CredentialManager/res/values-mn/strings.xml b/packages/CredentialManager/res/values-mn/strings.xml
index b0d4ca6b..a704ea0 100644
--- a/packages/CredentialManager/res/values-mn/strings.xml
+++ b/packages/CredentialManager/res/values-mn/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Бид нууц үггүй ирээдүй рүү урагшлахын хэрээр нууц үг нь нэвтрэх түлхүүрийн хамтаар боломжтой хэвээр байх болно."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"<xliff:g id="CREATETYPES">%1$s</xliff:g>-г хаана хадгалахаа сонгоно уу"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Мэдээллээ хадгалж, дараагийн удаа илүү хурдан нэвтрэхийн тулд нууц үгний менежерийг сонгоно уу"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g>-д нэвтрэхийн тулд нэвтрэх түлхүүр үүсгэх үү?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g>-д нэвтрэхийн тулд нууц үгийг хадгалах уу?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g>-н нэвтрэх мэдээллийг хадгалах уу?"</string>
<string name="passkey" msgid="632353688396759522">"passkey"</string>
<string name="password" msgid="6738570945182936667">"нууц үг"</string>
diff --git a/packages/CredentialManager/res/values-mr/strings.xml b/packages/CredentialManager/res/values-mr/strings.xml
index 5747afd..d3e14dd 100644
--- a/packages/CredentialManager/res/values-mr/strings.xml
+++ b/packages/CredentialManager/res/values-mr/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"पासवर्ड न वापरणाऱ्या भविष्यात पुढे जाताना, पासवर्ड तरीही पासकीच्या बरोबरीने उपलब्ध असतील."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"तुमची <xliff:g id="CREATETYPES">%1$s</xliff:g> कुठे सेव्ह करायची ते निवडा"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"तुमची माहिती सेव्ह करण्यासाठी आणि पुढच्या वेळी जलद साइन इन करण्याकरिता Password Manager निवडा"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> मध्ये साइन इन करण्यासाठी पासकी तयार करायची आहे का?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> मध्ये साइन इन करण्यासाठी पासवर्ड सेव्ह करायचा आहे का?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> साठी साइन-इन माहिती सेव्ह करायची का?"</string>
<string name="passkey" msgid="632353688396759522">"पासकी"</string>
<string name="password" msgid="6738570945182936667">"पासवर्ड"</string>
diff --git a/packages/CredentialManager/res/values-ms/strings.xml b/packages/CredentialManager/res/values-ms/strings.xml
index a6bc11d..a491177 100644
--- a/packages/CredentialManager/res/values-ms/strings.xml
+++ b/packages/CredentialManager/res/values-ms/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Meskipun masa depan kita nanti tidak memerlukan kata laluan, kata laluan masih akan tersedia bersama dengan kunci laluan."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Pilih tempat untuk menyimpan <xliff:g id="CREATETYPES">%1$s</xliff:g> anda"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Pilih Password Manager untuk menyimpan maklumat anda dan log masuk lebih pantas pada kali seterusnya"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Buat kunci laluan untuk log masuk ke <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Simpan kata laluan untuk log masuk ke <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Simpan maklumat log masuk untuk <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"kunci laluan"</string>
<string name="password" msgid="6738570945182936667">"kata laluan"</string>
diff --git a/packages/CredentialManager/res/values-my/strings.xml b/packages/CredentialManager/res/values-my/strings.xml
index 55eed78..b427a58 100644
--- a/packages/CredentialManager/res/values-my/strings.xml
+++ b/packages/CredentialManager/res/values-my/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"စကားဝှက်မသုံးခြင်း အနာဂတ်ဆီသို့ ရှေ့ဆက်ရာတွင် လျှို့ဝှက်ကီးများနှင့်အတူ စကားဝှက်များကို ဆက်လက်အသုံးပြုနိုင်ပါမည်။"</string>
<string name="choose_provider_title" msgid="8870795677024868108">"သင်၏ <xliff:g id="CREATETYPES">%1$s</xliff:g> သိမ်းရန်နေရာ ရွေးခြင်း"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"သင့်အချက်အလက်သိမ်းပြီး နောက်တစ်ကြိမ်၌ ပိုမိုမြန်ဆန်စွာ လက်မှတ်ထိုးဝင်ရန် စကားဝှက်မန်နေဂျာကို ရွေးပါ"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> သို့ လက်မှတ်ထိုးဝင်ရန် လျှို့ဝှက်ကီး ပြုလုပ်မလား။"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> သို့ လက်မှတ်ထိုးဝင်ရန် စကားဝှက်ကို သိမ်းမလား။"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> အတွက် လက်မှတ်ထိုးဝင်သည့်အချက်အလက်ကို သိမ်းမလား။"</string>
<string name="passkey" msgid="632353688396759522">"လျှို့ဝှက်ကီး"</string>
<string name="password" msgid="6738570945182936667">"စကားဝှက်"</string>
diff --git a/packages/CredentialManager/res/values-nb/strings.xml b/packages/CredentialManager/res/values-nb/strings.xml
index f7c5762..1e780ee5 100644
--- a/packages/CredentialManager/res/values-nb/strings.xml
+++ b/packages/CredentialManager/res/values-nb/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Vi går mot en fremtid uten passord, men passord fortsetter å være tilgjengelige ved siden av passnøkler."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Velg hvor du vil lagre <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Velg et verktøy for passordlagring for å lagre informasjonen din og logge på raskere neste gang"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Vil du opprette en passnøkkel for å logge på <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Vil du lagre passordet for å logge på <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Vil du lagre påloggingsinformasjon for <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"passnøkkel"</string>
<string name="password" msgid="6738570945182936667">"passord"</string>
diff --git a/packages/CredentialManager/res/values-ne/strings.xml b/packages/CredentialManager/res/values-ne/strings.xml
index bc3fc0e..55d4e87 100644
--- a/packages/CredentialManager/res/values-ne/strings.xml
+++ b/packages/CredentialManager/res/values-ne/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"हामी पासवर्डरहित भविष्यतर्फ बढ्दै गर्दा पासकीका साथसाथै पासवर्ड पनि उपलब्ध हुने छ।"</string>
<string name="choose_provider_title" msgid="8870795677024868108">"तपाईं आफ्ना <xliff:g id="CREATETYPES">%1$s</xliff:g> कहाँ सेभ गर्न चाहनुहुन्छ भन्ने कुरा छनौट गर्नुहोस्"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"कुनै पासवर्ड म्यानेजरमा आफ्नो जानकारी सेभ गरी अर्को पटक अझ छिटो साइन इन गर्नुहोस्"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> मा साइन इन गर्न पासकी बनाउने हो?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> मा साइन इन गर्न पासवर्ड सेभ गर्ने हो?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> मा साइन गर्न प्रयोग गरिनु पर्ने जानकारी सेभ गर्ने हो?"</string>
<string name="passkey" msgid="632353688396759522">"पासकी"</string>
<string name="password" msgid="6738570945182936667">"पासवर्ड"</string>
diff --git a/packages/CredentialManager/res/values-nl/strings.xml b/packages/CredentialManager/res/values-nl/strings.xml
index d4ce16b..53b3d70 100644
--- a/packages/CredentialManager/res/values-nl/strings.xml
+++ b/packages/CredentialManager/res/values-nl/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"We zijn op weg naar een wachtwoordloze toekomst, maar naast toegangssleutels kun je nog steeds gebruikmaken van wachtwoorden."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Kiezen waar je je <xliff:g id="CREATETYPES">%1$s</xliff:g> wilt opslaan"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Selecteer een wachtwoordmanager om je informatie op te slaan en de volgende keer sneller in te loggen"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Toegangssleutel maken om in te loggen bij <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Wachtwoord opslaan om in te loggen bij <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Inloggegevens opslaan voor <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"Toegangssleutel"</string>
<string name="password" msgid="6738570945182936667">"wachtwoord"</string>
diff --git a/packages/CredentialManager/res/values-or/strings.xml b/packages/CredentialManager/res/values-or/strings.xml
index 4ca9c39..ad268c9 100644
--- a/packages/CredentialManager/res/values-or/strings.xml
+++ b/packages/CredentialManager/res/values-or/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"ଆମେ ଏକ ପାସୱାର୍ଡବିହୀନ ଭବିଷ୍ୟତ ଆଡ଼କୁ ମୁଭ କରୁଥିବା ଯୋଗୁଁ ଏବେ ବି ପାସକୀଗୁଡ଼ିକ ସହିତ ପାସୱାର୍ଡ ଉପଲବ୍ଧ ହେବ।"</string>
<string name="choose_provider_title" msgid="8870795677024868108">"ଆପଣଙ୍କ <xliff:g id="CREATETYPES">%1$s</xliff:g> କେଉଁଠାରେ ସେଭ କରିବେ ତାହା ବାଛନ୍ତୁ"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"ଆପଣଙ୍କ ସୂଚନା ସେଭ କରି ପରବର୍ତ୍ତୀ ସମୟରେ ଶୀଘ୍ର ସାଇନ ଇନ କରିବା ପାଇଁ ଏକ Password Manager ଚୟନ କରନ୍ତୁ"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g>ରେ ସାଇନ ଇନ କରିବାକୁ ପାସକୀ ତିଆରି କରିବେ?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g>ରେ ସାଇନ ଇନ କରିବାକୁ ପାସୱାର୍ଡ ସେଭ କରିବେ?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> ପାଇଁ ସାଇନ-ଇନର ସୂଚନା ସେଭ କରିବେ?"</string>
<string name="passkey" msgid="632353688396759522">"ପାସକୀ"</string>
<string name="password" msgid="6738570945182936667">"ପାସୱାର୍ଡ"</string>
diff --git a/packages/CredentialManager/res/values-pa/strings.xml b/packages/CredentialManager/res/values-pa/strings.xml
index 414a4ce..8328d47 100644
--- a/packages/CredentialManager/res/values-pa/strings.xml
+++ b/packages/CredentialManager/res/values-pa/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"ਹਾਲਾਂਕਿ, ਅਸੀਂ ਪਾਸਵਰਡ ਰਹਿਤ ਭਵਿੱਖ ਵੱਲ ਵਧ ਰਹੇ ਹਾਂ, ਪਰ ਪਾਸਕੀਆਂ ਦੇ ਨਾਲ ਪਾਸਵਰਡ ਹਾਲੇ ਵੀ ਉਪਲਬਧ ਹੋਣਗੇ।"</string>
<string name="choose_provider_title" msgid="8870795677024868108">"ਚੁਣੋ ਕਿ ਆਪਣੀਆਂ <xliff:g id="CREATETYPES">%1$s</xliff:g> ਨੂੰ ਕਿੱਥੇ ਰੱਖਿਅਤ ਕਰਨਾ ਹੈ"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"ਆਪਣੀ ਜਾਣਕਾਰੀ ਨੂੰ ਰੱਖਿਅਤ ਕਰਨ ਅਤੇ ਅਗਲੀ ਵਾਰ ਤੇਜ਼ੀ ਨਾਲ ਸਾਈਨ-ਇਨ ਕਰਨ ਲਈ ਪਾਸਵਰਡ ਪ੍ਰਬੰਧਕ ਚੁਣੋ"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"ਕੀ <xliff:g id="APPNAME">%1$s</xliff:g> ਵਿੱਚ ਸਾਈਨ-ਇਨ ਕਰਨ ਲਈ ਪਾਸਕੀ ਬਣਾਉਣੀ ਹੈ?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"ਕੀ <xliff:g id="APPNAME">%1$s</xliff:g> ਵਿੱਚ ਸਾਈਨ-ਇਨ ਕਰਨ ਲਈ ਪਾਸਵਰਡ ਰੱਖਿਅਤ ਕਰਨਾ ਹੈ?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"ਕੀ <xliff:g id="APPNAME">%1$s</xliff:g> ਲਈ ਸਾਈਨ-ਇਨ ਜਾਣਕਾਰੀ ਰੱਖਿਅਤ ਕਰਨੀ ਹੈ?"</string>
<string name="passkey" msgid="632353688396759522">"ਪਾਸਕੀ"</string>
<string name="password" msgid="6738570945182936667">"ਪਾਸਵਰਡ"</string>
diff --git a/packages/CredentialManager/res/values-pl/strings.xml b/packages/CredentialManager/res/values-pl/strings.xml
index 91e2276..7114c3a 100644
--- a/packages/CredentialManager/res/values-pl/strings.xml
+++ b/packages/CredentialManager/res/values-pl/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"W czasie przechodzenia na technologie niewymagające haseł możliwość stosowania haseł – niezależnie od kluczy dostępu – wciąż będzie dostępna."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Wybierz, gdzie zapisywać <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Wybierz menedżera haseł, aby zapisywać informacje i logować się szybciej"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Utworzyć klucz dostępu do logowania w aplikacji <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Zapisać hasło do logowania w aplikacji <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Zapisać dane logowania do aplikacji <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"klucz"</string>
<string name="password" msgid="6738570945182936667">"hasło"</string>
diff --git a/packages/CredentialManager/res/values-pt-rBR/strings.xml b/packages/CredentialManager/res/values-pt-rBR/strings.xml
index 105441f..0b03f72 100644
--- a/packages/CredentialManager/res/values-pt-rBR/strings.xml
+++ b/packages/CredentialManager/res/values-pt-rBR/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Estamos avançando em direção a um futuro sem senhas, mas elas ainda vão estar disponíveis junto às chaves de acesso."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Escolha onde salvar suas <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Selecione um gerenciador de senhas para salvar suas informações e fazer login mais rapidamente na próxima vez"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Criar chave de acesso para fazer login no app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Salvar senha para fazer login no app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Salvar informações de login do app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"chave de acesso"</string>
<string name="password" msgid="6738570945182936667">"senha"</string>
diff --git a/packages/CredentialManager/res/values-pt-rPT/strings.xml b/packages/CredentialManager/res/values-pt-rPT/strings.xml
index f7259d8..b4cf374 100644
--- a/packages/CredentialManager/res/values-pt-rPT/strings.xml
+++ b/packages/CredentialManager/res/values-pt-rPT/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"À medida que avançamos para um futuro sem palavras-passe, as palavras-passe continuam disponíveis juntamente com as chaves de acesso."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Escolha onde guardar as suas <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Selecione um gestor de palavras-passe para guardar as suas informações e iniciar sessão mais rapidamente da próxima vez"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Criar a chave de acesso para iniciar sessão na app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Guardar a palavra-passe para iniciar sessão na app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Guardar as informações de início de sessão da app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"chave de acesso"</string>
<string name="password" msgid="6738570945182936667">"palavra-passe"</string>
diff --git a/packages/CredentialManager/res/values-pt/strings.xml b/packages/CredentialManager/res/values-pt/strings.xml
index 105441f..0b03f72 100644
--- a/packages/CredentialManager/res/values-pt/strings.xml
+++ b/packages/CredentialManager/res/values-pt/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Estamos avançando em direção a um futuro sem senhas, mas elas ainda vão estar disponíveis junto às chaves de acesso."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Escolha onde salvar suas <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Selecione um gerenciador de senhas para salvar suas informações e fazer login mais rapidamente na próxima vez"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Criar chave de acesso para fazer login no app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Salvar senha para fazer login no app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Salvar informações de login do app <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"chave de acesso"</string>
<string name="password" msgid="6738570945182936667">"senha"</string>
diff --git a/packages/CredentialManager/res/values-ro/strings.xml b/packages/CredentialManager/res/values-ro/strings.xml
index cfe61a9..8c865c4 100644
--- a/packages/CredentialManager/res/values-ro/strings.xml
+++ b/packages/CredentialManager/res/values-ro/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Ne îndreptăm spre un viitor fără parole, însă acestea sunt încă disponibile, alături de cheile de acces."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Alege unde dorești să salvezi <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Selectează un manager de parole pentru a salva informațiile și a te conecta mai rapid data viitoare"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Creezi o cheie de acces pentru a te conecta la <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Salvezi parola pentru a te conecta la <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Salvezi informațiile de conectare pentru <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"cheia de acces"</string>
<string name="password" msgid="6738570945182936667">"parolă"</string>
diff --git a/packages/CredentialManager/res/values-ru/strings.xml b/packages/CredentialManager/res/values-ru/strings.xml
index ba7d34a..92eea32 100644
--- a/packages/CredentialManager/res/values-ru/strings.xml
+++ b/packages/CredentialManager/res/values-ru/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Хотя движение к будущему без паролей уже началось, их по-прежнему можно будет использовать наряду с ключами доступа."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Укажите, куда нужно сохранить <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Выберите менеджер паролей, чтобы сохранять учетные данные и быстро выполнять вход."</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Создать ключ доступа для входа в приложение \"<xliff:g id="APPNAME">%1$s</xliff:g>\"?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Сохранить ключ доступа для входа в приложение \"<xliff:g id="APPNAME">%1$s</xliff:g>\"?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Сохранить учетные данные для приложения \"<xliff:g id="APPNAME">%1$s</xliff:g>\"?"</string>
<string name="passkey" msgid="632353688396759522">"ключ доступа"</string>
<string name="password" msgid="6738570945182936667">"пароль"</string>
diff --git a/packages/CredentialManager/res/values-si/strings.xml b/packages/CredentialManager/res/values-si/strings.xml
index 03ada97..ec47018 100644
--- a/packages/CredentialManager/res/values-si/strings.xml
+++ b/packages/CredentialManager/res/values-si/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"අපි මුරපද රහිත අනාගතයක් කරා ගමන් කරන විට, මුරයතුරු සමග මුරපද තවමත් පවතී."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"ඔබේ <xliff:g id="CREATETYPES">%1$s</xliff:g> සුරැකිය යුතු ස්ථානය තෝරා ගන්න"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"ඔබේ තතු සුරැකීමට සහ මීළඟ වතාවේ වේගයෙන් පුරනය වීමට මුරපද කළමනාකරුවෙකු තෝරන්න"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> වෙත පුරනය වීමට මුරයතුරක් තනන්න ද?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> වෙත පුරනය වීමට මුරපදය සුරකින්න ද?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> සඳහා පුරනය වීමේ තතු සුරකින්න ද?"</string>
<string name="passkey" msgid="632353688396759522">"මුරයතුර"</string>
<string name="password" msgid="6738570945182936667">"මුරපදය"</string>
diff --git a/packages/CredentialManager/res/values-sk/strings.xml b/packages/CredentialManager/res/values-sk/strings.xml
index 7198625..7426c97 100644
--- a/packages/CredentialManager/res/values-sk/strings.xml
+++ b/packages/CredentialManager/res/values-sk/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Blížime sa k budúcnosti bez hesiel, ale heslá budú popri prístupových kľúčoch stále k dispozícii."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Vyberte, kam sa majú ukladať <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Vyberte správcu hesiel, do ktorého sa budú ukladať vaše údaje, aby ste sa nabudúce mohli rýchlejšie prihlásiť"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Chcete vytvoriť prístupový kľúč na prihlasovanie do aplikácie <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Chcete uložiť heslo na prihlasovanie do aplikácie <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Chcete uložiť prihlasovacie údaje pre aplikáciu <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"prístupový kľúč"</string>
<string name="password" msgid="6738570945182936667">"heslo"</string>
diff --git a/packages/CredentialManager/res/values-sl/strings.xml b/packages/CredentialManager/res/values-sl/strings.xml
index 3ff85f0..8e08512 100644
--- a/packages/CredentialManager/res/values-sl/strings.xml
+++ b/packages/CredentialManager/res/values-sl/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Na poti v prihodnost brez gesel bodo poleg ključev za dostop še vedno v uporabi tudi gesla."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Izbira mesta za shranjevanje <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Izberite upravitelja gesel za shranjevanje podatkov za prijavo, da se boste naslednjič lahko hitreje prijavili."</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Želite ustvariti ključ za dostop za prijavo v aplikacijo <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Želite shraniti geslo za prijavo v aplikacijo <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Želite shraniti podatke za prijavo za aplikacijo <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"ključ za dostop"</string>
<string name="password" msgid="6738570945182936667">"geslo"</string>
diff --git a/packages/CredentialManager/res/values-sq/strings.xml b/packages/CredentialManager/res/values-sq/strings.xml
index 41f6391..96f1ff0 100644
--- a/packages/CredentialManager/res/values-sq/strings.xml
+++ b/packages/CredentialManager/res/values-sq/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Teksa shkojmë drejt një të ardhmeje pa fjalëkalime, këto të fundit do të ofrohen ende së bashku me çelësat e kalimit."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Zgjidh se ku t\'i ruash <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Zgjidh një menaxher fjalëkalimesh për të ruajtur informacionet e tua dhe për t\'u identifikuar më shpejt herën tjetër"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Të krijohet një çelës kalimit për t\'u identifikuar në <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Të ruhet fjalëkalimi për t\'u identifikuar në <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Të ruhen informacionet e identifikimit për <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"çelësin e kalimit"</string>
<string name="password" msgid="6738570945182936667">"fjalëkalimi"</string>
diff --git a/packages/CredentialManager/res/values-sr/strings.xml b/packages/CredentialManager/res/values-sr/strings.xml
index 1a5567f..dae62bc 100644
--- a/packages/CredentialManager/res/values-sr/strings.xml
+++ b/packages/CredentialManager/res/values-sr/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Како се крећемо ка будућности без лозинки, лозинке ће и даље бити доступне уз приступне кодове."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Одаберите где ћете сачувати: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Изаберите менаџера лозинки да бисте сачували податке и брже се пријавили следећи пут"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Желите да направите приступни кључ да бисте се пријавили у <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Желите да сачувате лозинку да бисте се пријавили у <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Желите да сачувате податке за пријављивање за: <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"приступни кôд"</string>
<string name="password" msgid="6738570945182936667">"лозинка"</string>
diff --git a/packages/CredentialManager/res/values-sv/strings.xml b/packages/CredentialManager/res/values-sv/strings.xml
index 8937b01..2326497 100644
--- a/packages/CredentialManager/res/values-sv/strings.xml
+++ b/packages/CredentialManager/res/values-sv/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Medan vi beger oss mot en lösenordslös framtid kommer lösenord fortfarande att vara tillgängliga utöver nycklar."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Välj var du vill spara <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Välj en lösenordshanterare för att spara dina uppgifter och logga in snabbare nästa gång"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Vill du skapa en nyckel för att logga in i <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Vill du spara lösenordet för att logga in i <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Vill du spara inloggningsuppgifterna för <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"nyckel"</string>
<string name="password" msgid="6738570945182936667">"lösenord"</string>
diff --git a/packages/CredentialManager/res/values-sw/strings.xml b/packages/CredentialManager/res/values-sw/strings.xml
index 051122f..6c39509 100644
--- a/packages/CredentialManager/res/values-sw/strings.xml
+++ b/packages/CredentialManager/res/values-sw/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Tunavyoelekea katika enzi isiyo ya manenosiri, manenosiri yataendelea kupatikana pamoja na funguo za siri."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Chagua ambako unahifadhi <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Chagua kidhibiti cha manenosiri ili uhifadhi taarifa zako na uingie kwenye akaunti kwa urahisi wakati mwingine"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Ungependa kuunda ufunguo wa siri ili uingie katika <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Ungependa kuhifadhi nenosiri ili uingie katika <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Ungependa kuhifadhi maelezo ya kuingia katika akaunti kwa ajili ya <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"ufunguo wa siri"</string>
<string name="password" msgid="6738570945182936667">"nenosiri"</string>
diff --git a/packages/CredentialManager/res/values-ta/strings.xml b/packages/CredentialManager/res/values-ta/strings.xml
index 64d4a24..6feb66f 100644
--- a/packages/CredentialManager/res/values-ta/strings.xml
+++ b/packages/CredentialManager/res/values-ta/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"கடவுச்சொல்லற்ற எதிர்காலத்தை நோக்கி நாம் பயணிக்கிறோம். கடவுச்சாவிகளைப் பயன்படுத்தும் இதே வேளையில் கடவுச்சொற்களையும் பயன்படுத்த முடியும்."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"உங்கள் <xliff:g id="CREATETYPES">%1$s</xliff:g> எங்கே சேமிக்கப்பட வேண்டும் என்பதைத் தேர்வுசெய்யுங்கள்"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"உங்கள் தகவல்களைச் சேமித்து அடுத்த முறை விரைவாக உள்நுழைய ஒரு கடவுச்சொல் நிர்வாகியைத் தேர்வுசெய்யுங்கள்"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> ஆப்ஸில் உள்நுழைய கடவுச்சாவியை உருவாக்கவா?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> ஆப்ஸில் உள்நுழைய கடவுச்சொல்லைச் சேமிக்கவா?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> ஆப்ஸுக்கான உள்நுழைவு விவரங்களைச் சேமிக்கவா?"</string>
<string name="passkey" msgid="632353688396759522">"கடவுச்சாவி"</string>
<string name="password" msgid="6738570945182936667">"கடவுச்சொல்"</string>
diff --git a/packages/CredentialManager/res/values-te/strings.xml b/packages/CredentialManager/res/values-te/strings.xml
index 066f785..bf3c1e0 100644
--- a/packages/CredentialManager/res/values-te/strings.xml
+++ b/packages/CredentialManager/res/values-te/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"మనం భవిష్యత్తులో పాస్వర్డ్ రహిత టెక్నాలజీని ఉపయోగించినా, పాస్కీలతో పాటు పాస్వర్డ్లు కూడా అందుబాటులో ఉంటాయి."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"మీ <xliff:g id="CREATETYPES">%1$s</xliff:g> ఎక్కడ సేవ్ చేయాలో ఎంచుకోండి"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"తర్వాతిసారి మరింత వేగంగా సైన్ ఇన్ చేసేందుకు వీలుగా మీ సమాచారాన్ని సేవ్ చేయడం కోసం ఒక పాస్వర్డ్ మేనేజర్ను ఎంచుకోండి"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g>కు సైన్ ఇన్ చేయడానికి పాస్-కీని క్రియేట్ చేయాలా?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g>కు సైన్ ఇన్ చేయడానికి పాస్వర్డ్ను సేవ్ చేయాలా?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> కోసం సైన్ ఇన్ సమాచారాన్ని సేవ్ చేయాలా?"</string>
<string name="passkey" msgid="632353688396759522">"పాస్-కీ"</string>
<string name="password" msgid="6738570945182936667">"పాస్వర్డ్"</string>
diff --git a/packages/CredentialManager/res/values-th/strings.xml b/packages/CredentialManager/res/values-th/strings.xml
index 783d057..4fc8ba0e 100644
--- a/packages/CredentialManager/res/values-th/strings.xml
+++ b/packages/CredentialManager/res/values-th/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"ในขณะที่เราก้าวไปสู่อนาคตที่ไม่ต้องใช้รหัสผ่านนั้น รหัสผ่านจะยังคงใช้ได้อยู่ควบคู่ไปกับการเปลี่ยนไปใช้พาสคีย์"</string>
<string name="choose_provider_title" msgid="8870795677024868108">"เลือกว่าต้องการบันทึก<xliff:g id="CREATETYPES">%1$s</xliff:g>ไว้ที่ใด"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"เลือกเครื่องมือจัดการรหัสผ่านเพื่อบันทึกข้อมูลและลงชื่อเข้าใช้เร็วขึ้นในครั้งถัดไป"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"สร้างพาสคีย์เพื่อลงชื่อเข้าใช้ <xliff:g id="APPNAME">%1$s</xliff:g> ไหม"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"บันทึกรหัสผ่านเพื่อลงชื่อเข้าใช้ <xliff:g id="APPNAME">%1$s</xliff:g> ไหม"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"บันทึกข้อมูลการลงชื่อเข้าใช้สำหรับ <xliff:g id="APPNAME">%1$s</xliff:g> ไหม"</string>
<string name="passkey" msgid="632353688396759522">"พาสคีย์"</string>
<string name="password" msgid="6738570945182936667">"รหัสผ่าน"</string>
diff --git a/packages/CredentialManager/res/values-tl/strings.xml b/packages/CredentialManager/res/values-tl/strings.xml
index 18283ea..e6bcadd 100644
--- a/packages/CredentialManager/res/values-tl/strings.xml
+++ b/packages/CredentialManager/res/values-tl/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Habang lumalayo tayo sa mga password, magiging available pa rin ang mga password kasama ng mga passkey."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Piliin kung saan mo ise-save ang iyong <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Pumili ng password manager para ma-save ang iyong impormasyon at makapag-sign in nang mas mabilis sa susunod na pagkakataon"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Gumawa ng passkey para mag-sign in sa <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"I-save ang password para mag-sign in sa <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"I-save ang impormasyon sa pag-sign in para sa <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"passkey"</string>
<string name="password" msgid="6738570945182936667">"password"</string>
diff --git a/packages/CredentialManager/res/values-tr/strings.xml b/packages/CredentialManager/res/values-tr/strings.xml
index 8778797..f9455e7 100644
--- a/packages/CredentialManager/res/values-tr/strings.xml
+++ b/packages/CredentialManager/res/values-tr/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Şifresiz bir geleceğe doğru ilerlerken şifreler, geçiş anahtarlarıyla birlikte kullanılmaya devam edecektir."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"<xliff:g id="CREATETYPES">%1$s</xliff:g> kaydedileceği yeri seçin"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Bilgilerinizi kaydedip bir dahaki sefere daha hızlı oturum açmak için bir şifre yöneticisi seçin"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> uygulamasında oturum açmak için geçiş anahtarı oluşturulsun mu?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> uygulamasında oturum açmak için şifre kaydedilsin mi?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> için oturum açma bilgileri kaydedilsin mi?"</string>
<string name="passkey" msgid="632353688396759522">"Geçiş anahtarı"</string>
<string name="password" msgid="6738570945182936667">"Şifre"</string>
diff --git a/packages/CredentialManager/res/values-uk/strings.xml b/packages/CredentialManager/res/values-uk/strings.xml
index 53de1b3..e804777 100644
--- a/packages/CredentialManager/res/values-uk/strings.xml
+++ b/packages/CredentialManager/res/values-uk/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"На шляху до безпарольного майбутнього паролі й надалі будуть використовуватися паралельно з ключами доступу."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Виберіть, де зберігати <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Виберіть менеджер паролів, щоб зберігати свої дані й надалі входити в облікові записи швидше"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Створити ключ доступу для входу в додаток <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Зберегти пароль для входу в додаток <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Зберегти дані для входу для додатка <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"ключ доступу"</string>
<string name="password" msgid="6738570945182936667">"пароль"</string>
diff --git a/packages/CredentialManager/res/values-ur/strings.xml b/packages/CredentialManager/res/values-ur/strings.xml
index 47e33fb..9562fdf 100644
--- a/packages/CredentialManager/res/values-ur/strings.xml
+++ b/packages/CredentialManager/res/values-ur/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"چونکہ ہم بغیر پاس ورڈ والے مستقبل کی طرف جا رہے ہیں اس کے باوجود پاس ورڈز پاس کیز کے ساتھ ہی دستیاب ہوں گے۔"</string>
<string name="choose_provider_title" msgid="8870795677024868108">"منتخب کریں کہ آپ کی <xliff:g id="CREATETYPES">%1$s</xliff:g> کو کہاں محفوظ کرنا ہے"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"اپنی معلومات کو محفوظ کرنے اور اگلی بار تیزی سے سائن ان کرنے کے لیے پاس ورڈ مینیجر منتخب کریں"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> میں سائن ان کرنے کیلئے پاس کی تخلیق کریں؟"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> میں سائن ان کرنے کیلئے پاس ورڈ محفوظ کریں؟"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> کے لیے سائن ان کی معلومات محفوظ کریں؟"</string>
<string name="passkey" msgid="632353688396759522">"پاس کی"</string>
<string name="password" msgid="6738570945182936667">"پاس ورڈ"</string>
diff --git a/packages/CredentialManager/res/values-uz/strings.xml b/packages/CredentialManager/res/values-uz/strings.xml
index 6025cae0..4e27d3e7 100644
--- a/packages/CredentialManager/res/values-uz/strings.xml
+++ b/packages/CredentialManager/res/values-uz/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Parolsiz kelajak sari harakatlanar ekanmiz, parollar kalitlar bilan birga ishlatilishda davom etadi."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Bu <xliff:g id="CREATETYPES">%1$s</xliff:g> qayerga saqlanishini tanlang"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Maʼlumotlaringizni saqlash va keyingi safar tez kirish uchun parollar menejerini tanlang"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"<xliff:g id="APPNAME">%1$s</xliff:g> ilovasiga kirish uchun kirish kaliti yaratilsinmi?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"<xliff:g id="APPNAME">%1$s</xliff:g> ilovasiga kirish uchun parol saqlansinmi?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> uchun kirish maʼlumoti saqlansinmi?"</string>
<string name="passkey" msgid="632353688396759522">"kalit"</string>
<string name="password" msgid="6738570945182936667">"parol"</string>
diff --git a/packages/CredentialManager/res/values-vi/strings.xml b/packages/CredentialManager/res/values-vi/strings.xml
index 1411036..c774b93 100644
--- a/packages/CredentialManager/res/values-vi/strings.xml
+++ b/packages/CredentialManager/res/values-vi/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Trong quá trình chúng tôi hướng đến tương lai không dùng mật khẩu, bạn vẫn sẽ dùng được mật khẩu cùng với khoá truy cập."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Chọn vị trí lưu <xliff:g id="CREATETYPES">%1$s</xliff:g> của bạn"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Hãy chọn một trình quản lý mật khẩu để lưu thông tin của bạn và đăng nhập nhanh hơn vào lần tới"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Tạo khoá truy cập để đăng nhập vào <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Lưu mật khẩu để đăng nhập vào <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Lưu thông tin đăng nhập cho <xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"khoá đăng nhập"</string>
<string name="password" msgid="6738570945182936667">"mật khẩu"</string>
diff --git a/packages/CredentialManager/res/values-zh-rCN/strings.xml b/packages/CredentialManager/res/values-zh-rCN/strings.xml
index dcc2269..11448e9 100644
--- a/packages/CredentialManager/res/values-zh-rCN/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rCN/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"在我们向无密码未来迈进的过程中,密码仍会与通行密钥并行使用。"</string>
<string name="choose_provider_title" msgid="8870795677024868108">"选择保存<xliff:g id="CREATETYPES">%1$s</xliff:g>的位置"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"请选择一款密码管理工具来保存您的信息,以便下次更快地登录"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"要创建通行密钥以便登录 <xliff:g id="APPNAME">%1$s</xliff:g> 吗?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"要保存密码以便登录 <xliff:g id="APPNAME">%1$s</xliff:g> 吗?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"要保存“<xliff:g id="APPNAME">%1$s</xliff:g>”的登录信息吗?"</string>
<string name="passkey" msgid="632353688396759522">"通行密钥"</string>
<string name="password" msgid="6738570945182936667">"密码"</string>
diff --git a/packages/CredentialManager/res/values-zh-rHK/strings.xml b/packages/CredentialManager/res/values-zh-rHK/strings.xml
index 5e893f6..8f69643 100644
--- a/packages/CredentialManager/res/values-zh-rHK/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rHK/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"我們將會改用無密碼技術,而密碼仍可與密鑰並行使用。"</string>
<string name="choose_provider_title" msgid="8870795677024868108">"選擇儲存<xliff:g id="CREATETYPES">%1$s</xliff:g>的位置"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"選取密碼管理工具即可儲存自己的資料,縮短下次登入的時間"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"要建立密鑰以登入「<xliff:g id="APPNAME">%1$s</xliff:g>」嗎?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"要儲存密碼以登入「<xliff:g id="APPNAME">%1$s</xliff:g>」嗎?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"要儲存「<xliff:g id="APPNAME">%1$s</xliff:g>」的登入資料嗎?"</string>
<string name="passkey" msgid="632353688396759522">"密鑰"</string>
<string name="password" msgid="6738570945182936667">"密碼"</string>
diff --git a/packages/CredentialManager/res/values-zh-rTW/strings.xml b/packages/CredentialManager/res/values-zh-rTW/strings.xml
index 1e1dca4..b5fa62c 100644
--- a/packages/CredentialManager/res/values-zh-rTW/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rTW/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"我們日後將改採無密碼技術,密碼仍可與密碼金鑰並行使用。"</string>
<string name="choose_provider_title" msgid="8870795677024868108">"選擇要將<xliff:g id="CREATETYPES">%1$s</xliff:g>存在哪裡"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"選取密碼管理工具並儲存資訊,下次就能更快登入"</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"要建立密碼金鑰以登入「<xliff:g id="APPNAME">%1$s</xliff:g>」嗎?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"要儲存密碼以登入「<xliff:g id="APPNAME">%1$s</xliff:g>」嗎?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"要儲存「<xliff:g id="APPNAME">%1$s</xliff:g>」的登入資訊嗎?"</string>
<string name="passkey" msgid="632353688396759522">"密碼金鑰"</string>
<string name="password" msgid="6738570945182936667">"密碼"</string>
diff --git a/packages/CredentialManager/res/values-zu/strings.xml b/packages/CredentialManager/res/values-zu/strings.xml
index 72a1e8f..5915a73 100644
--- a/packages/CredentialManager/res/values-zu/strings.xml
+++ b/packages/CredentialManager/res/values-zu/strings.xml
@@ -39,10 +39,8 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Njengoba sibhekela kwikusasa elingenaphasiwedi, amagama ayimfihlo asazotholakala eceleni kokhiye bokudlula."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"Khetha lapho ongagcina khona i-<xliff:g id="CREATETYPES">%1$s</xliff:g> yakho"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Khetha isiphathi sephasiwedi ukuze ulondoloze ulwazi lwakho futhi ungene ngemvume ngokushesha ngesikhathi esizayo."</string>
- <!-- no translation found for choose_create_option_passkey_title (7980430650778623135) -->
- <skip />
- <!-- no translation found for choose_create_option_password_title (6238446571944651980) -->
- <skip />
+ <string name="choose_create_option_passkey_title" msgid="7980430650778623135">"Sungula ukhiye wokudlula ukuze ungene ngemvume ku-<xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="6238446571944651980">"Londoloza iphasiwedi ukuze ungene ngemvume ku-<xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"Londoloza ulwazi lokungena lwe-<xliff:g id="APPNAME">%1$s</xliff:g>?"</string>
<string name="passkey" msgid="632353688396759522">"ukhiye wokudlula"</string>
<string name="password" msgid="6738570945182936667">"iphasiwedi"</string>
diff --git a/packages/CredentialManager/res/values/colors.xml b/packages/CredentialManager/res/values/colors.xml
index b4d2eeb..9d31b35 100644
--- a/packages/CredentialManager/res/values/colors.xml
+++ b/packages/CredentialManager/res/values/colors.xml
@@ -15,14 +15,12 @@
-->
<!-- Color palette -->
-<resources>
- <!-- These colors are used for Remote Views. -->
- <color name="text_primary">#1A1B20</color>
- <color name="text_secondary">#44474F</color>
- <color name="dropdown_container">#F3F3FA</color>
- <color name="sign_in_options_container">#DADADA</color>
- <color name="sign_in_options_icon_color">#1B1B1B</color>
+<resources
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
- <!-- These colors are used for Inline Suggestions. -->
- <color name="inline_background">#FFFFFF</color>
+ <!-- These colors are used for Remote Views. -->
+ <color name="onSurface">?androidprv:attr/materialColorOnSurface</color>
+ <color name="onSurfaceVariant">?androidprv:attr/materialColorOnSurfaceVariant</color>
+ <color name="surfaceDim">?androidprv:attr/materialColorSurfaceDim</color>
+ <color name="surfaceContainer">?androidprv:attr/materialColorSurfaceContainer</color>
</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/dimens.xml b/packages/CredentialManager/res/values/dimens.xml
index 82dee5c..314437e 100644
--- a/packages/CredentialManager/res/values/dimens.xml
+++ b/packages/CredentialManager/res/values/dimens.xml
@@ -18,16 +18,18 @@
<resources>
<dimen name="autofill_view_top_padding">12dp</dimen>
- <dimen name="autofill_view_right_padding">12dp</dimen>
+ <dimen name="autofill_view_right_padding">16dp</dimen>
<dimen name="autofill_view_bottom_padding">12dp</dimen>
<dimen name="autofill_view_left_padding">16dp</dimen>
- <dimen name="autofill_view_icon_to_text_padding">10dp</dimen>
+ <dimen name="autofill_view_icon_to_text_padding">16dp</dimen>
+ <dimen name="autofill_view_end_items_padding">8dp</dimen>
+ <dimen name="more_options_item_vertical_padding">14dp</dimen>
<dimen name="autofill_icon_size">24dp</dimen>
<dimen name="autofill_dropdown_textview_min_width">112dp</dimen>
<dimen name="autofill_dropdown_textview_max_width">230dp</dimen>
<dimen name="dropdown_layout_horizontal_margin">24dp</dimen>
<integer name="autofill_max_visible_datasets">4</integer>
- <dimen name="dropdown_touch_target_min_height">48dp</dimen>
+ <dimen name="dropdown_touch_target_min_height">49dp</dimen>
<dimen name="horizontal_chip_padding">8dp</dimen>
<dimen name="vertical_chip_padding">6dp</dimen>
</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index d7a2e36..6ba684d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -295,8 +295,9 @@
}
var dropdownPresentation: RemoteViews? = null
if (i < maxDatasetDisplayLimit) {
- dropdownPresentation = RemoteViewsFactory
- .createDropdownPresentation(this, icon, primaryEntry)
+ dropdownPresentation = RemoteViewsFactory.createDropdownPresentation(
+ this, icon, primaryEntry, /*isFirstEntry= */ i == 0,
+ /*isLastEntry= */ (totalEntryCount - i == 1))
}
val dataSetBuilder = Dataset.Builder()
@@ -572,6 +573,12 @@
sessionId: Int
) {
viewNode.autofillId?.let {
+ val domain = viewNode.webDomain
+ val request = viewNode.pendingCredentialRequest
+ if (domain != null && request != null) {
+ responseClientState.putBoolean(
+ WEBVIEW_REQUESTED_CREDENTIAL_KEY, true)
+ }
cmRequests.addAll(getCredentialOptionsFromViewNode(viewNode, it, responseClientState,
traversedViewNodes, credentialOptionsFromHints, sessionId))
traversedViewNodes.add(it)
@@ -597,8 +604,8 @@
sessionId: Int
): MutableList<CredentialOption> {
val credentialOptions: MutableList<CredentialOption> = mutableListOf()
- if (Flags.autofillCredmanDevIntegration() && viewNode.credentialManagerRequest != null) {
- viewNode.credentialManagerRequest
+ if (Flags.autofillCredmanDevIntegration() && viewNode.pendingCredentialRequest != null) {
+ viewNode.pendingCredentialRequest
?.getCredentialOptions()
?.forEach { credentialOption ->
credentialOption.candidateQueryData
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index 965ee86..99a9409 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -21,6 +21,7 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@@ -94,7 +95,7 @@
label = {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
- modifier = Modifier.fillMaxWidth().padding(
+ modifier = Modifier.fillMaxWidth().heightIn(min = 56.dp).padding(
// Total end padding should be 16dp, but the suggestion chip itself
// has 8dp horizontal elements padding
horizontal = 8.dp, vertical = 16.dp,
@@ -258,10 +259,14 @@
onClick = onClick,
shape = Shapes.large,
label = {
- Column(modifier = Modifier.wrapContentSize()
- .padding(start = 16.dp, top = 16.dp, bottom = 16.dp)) {
+ Column(
+ modifier = Modifier.heightIn(min = 56.dp).wrapContentSize().padding(
+ start = 16.dp, top = 16.dp, bottom = 16.dp
+ ),
+ verticalArrangement = Arrangement.Center,
+ ) {
SmallTitleText(entryHeadlineText)
- if (entrySecondLineText != null && entrySecondLineText.isNotEmpty()) {
+ if (!entrySecondLineText.isNullOrBlank()) {
BodySmallText(entrySecondLineText)
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
index 7966a86..a46e358 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
@@ -31,12 +31,13 @@
private const val setMaxHeightMethodName = "setMaxHeight"
private const val setBackgroundResourceMethodName = "setBackgroundResource"
private const val bulletPoint = "\u2022"
- private const val passwordCharacterLength = 15
fun createDropdownPresentation(
context: Context,
icon: Icon,
- credentialEntryInfo: CredentialEntryInfo
+ credentialEntryInfo: CredentialEntryInfo,
+ isFirstEntry: Boolean,
+ isLastEntry: Boolean,
): RemoteViews {
var layoutId: Int = com.android.credentialmanager.R.layout
.credman_dropdown_presentation_layout
@@ -44,7 +45,6 @@
if (credentialEntryInfo.credentialType == CredentialType.UNKNOWN) {
return remoteViews
}
- setRemoteViewsPaddings(remoteViews, context, /* primaryTextBottomPadding=*/0)
val displayName = credentialEntryInfo.displayName ?: credentialEntryInfo.userName
remoteViews.setTextViewText(android.R.id.text1, displayName)
val secondaryText =
@@ -56,12 +56,6 @@
else (credentialEntryInfo.credentialTypeDisplayName + " " + bulletPoint + " "
+ credentialEntryInfo.providerDisplayName)
remoteViews.setTextViewText(android.R.id.text2, secondaryText)
- val textColorPrimary = ContextCompat.getColor(context,
- com.android.credentialmanager.R.color.text_primary)
- remoteViews.setTextColor(android.R.id.text1, textColorPrimary)
- val textColorSecondary = ContextCompat.getColor(context, com.android
- .credentialmanager.R.color.text_secondary)
- remoteViews.setTextColor(android.R.id.text2, textColorSecondary)
remoteViews.setImageViewIcon(android.R.id.icon1, icon);
remoteViews.setBoolean(
android.R.id.icon1, setAdjustViewBoundsMethodName, true);
@@ -73,9 +67,23 @@
remoteViews.setContentDescription(android.R.id.icon1, credentialEntryInfo
.providerDisplayName);
val drawableId =
- com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one
+ if (isFirstEntry)
+ com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one else
+ com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_middle
remoteViews.setInt(
android.R.id.content, setBackgroundResourceMethodName, drawableId);
+ if (isFirstEntry) remoteViews.setViewPadding(
+ com.android.credentialmanager.R.id.credential_card,
+ /* left=*/0,
+ /* top=*/8,
+ /* right=*/0,
+ /* bottom=*/0)
+ if (isLastEntry) remoteViews.setViewPadding(
+ com.android.credentialmanager.R.id.credential_card,
+ /*left=*/0,
+ /* top=*/0,
+ /* right=*/0,
+ /* bottom=*/8)
return remoteViews
}
@@ -83,19 +91,9 @@
var layoutId: Int = com.android.credentialmanager.R.layout
.credman_dropdown_bottom_sheet
val remoteViews = RemoteViews(context.packageName, layoutId)
- setRemoteViewsPaddings(remoteViews, context)
remoteViews.setTextViewText(android.R.id.text1, ContextCompat.getString(context,
com.android.credentialmanager
.R.string.dropdown_presentation_more_sign_in_options_text))
-
- val textColorPrimary = ContextCompat.getColor(context,
- com.android.credentialmanager.R.color.text_primary)
- remoteViews.setTextColor(android.R.id.text1, textColorPrimary)
- val icon = Icon.createWithResource(context, com
- .android.credentialmanager.R.drawable.more_horiz_24px)
- icon.setTint(ContextCompat.getColor(context,
- com.android.credentialmanager.R.color.sign_in_options_icon_color))
- remoteViews.setImageViewIcon(android.R.id.icon1, icon)
remoteViews.setBoolean(
android.R.id.icon1, setAdjustViewBoundsMethodName, true);
remoteViews.setInt(
@@ -109,50 +107,5 @@
android.R.id.content, setBackgroundResourceMethodName, drawableId);
return remoteViews
}
-
- private fun setRemoteViewsPaddings(
- remoteViews: RemoteViews, context: Context) {
- val bottomPadding = context.resources.getDimensionPixelSize(
- com.android.credentialmanager.R.dimen.autofill_view_bottom_padding)
- setRemoteViewsPaddings(remoteViews, context, bottomPadding)
- }
-
- private fun setRemoteViewsPaddings(
- remoteViews: RemoteViews, context: Context, primaryTextBottomPadding: Int) {
- val leftPadding = context.resources.getDimensionPixelSize(
- com.android.credentialmanager.R.dimen.autofill_view_left_padding)
- val iconToTextPadding = context.resources.getDimensionPixelSize(
- com.android.credentialmanager.R.dimen.autofill_view_icon_to_text_padding)
- val rightPadding = context.resources.getDimensionPixelSize(
- com.android.credentialmanager.R.dimen.autofill_view_right_padding)
- val topPadding = context.resources.getDimensionPixelSize(
- com.android.credentialmanager.R.dimen.autofill_view_top_padding)
- val bottomPadding = context.resources.getDimensionPixelSize(
- com.android.credentialmanager.R.dimen.autofill_view_bottom_padding)
- remoteViews.setViewPadding(
- android.R.id.icon1,
- leftPadding,
- /* top=*/0,
- /* right=*/0,
- /* bottom=*/0)
- remoteViews.setViewPadding(
- android.R.id.text1,
- iconToTextPadding,
- /* top=*/topPadding,
- /* right=*/rightPadding,
- primaryTextBottomPadding)
- remoteViews.setViewPadding(
- android.R.id.text2,
- iconToTextPadding,
- /* top=*/0,
- /* right=*/rightPadding,
- /* bottom=*/bottomPadding)
- }
-
- private fun isDarkMode(context: Context): Boolean {
- val currentNightMode = context.resources.configuration.uiMode and
- Configuration.UI_MODE_NIGHT_MASK
- return currentNightMode == Configuration.UI_MODE_NIGHT_YES
- }
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index bc0ea02..b9c9d89 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -18,7 +18,6 @@
import android.credentials.flags.Flags.selectorUiImprovementsEnabled
import android.graphics.drawable.Drawable
-import android.text.TextUtils
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.result.ActivityResult
import androidx.activity.result.IntentSenderRequest
@@ -431,6 +430,14 @@
val hasSingleEntry = primaryPageCredentialEntryList.size +
primaryPageLockedEntryList.size == 1
+ val areAllPasswordsOnPrimaryScreen = primaryPageLockedEntryList.isEmpty() &&
+ primaryPageCredentialEntryList.all {
+ it.sortedCredentialEntryList.first().credentialType == CredentialType.PASSWORD
+ }
+ val areAllPasskeysOnPrimaryScreen = primaryPageLockedEntryList.isEmpty() &&
+ primaryPageCredentialEntryList.all {
+ it.sortedCredentialEntryList.first().credentialType == CredentialType.PASSKEY
+ }
item {
if (requestDisplayInfo.preferIdentityDocUi) {
HeadlineText(
@@ -447,28 +454,30 @@
HeadlineText(
text = stringResource(
if (hasSingleEntry) {
- val singleEntryType = primaryPageCredentialEntryList.firstOrNull()
- ?.sortedCredentialEntryList?.firstOrNull()?.credentialType
- if (singleEntryType == CredentialType.PASSKEY)
+ if (areAllPasskeysOnPrimaryScreen)
R.string.get_dialog_title_use_passkey_for
- else if (singleEntryType == CredentialType.PASSWORD)
+ else if (areAllPasswordsOnPrimaryScreen)
R.string.get_dialog_title_use_password_for
else if (authenticationEntryList.isNotEmpty())
R.string.get_dialog_title_unlock_options_for
else R.string.get_dialog_title_use_sign_in_for
} else {
- if (authenticationEntryList.isNotEmpty() ||
- sortedUserNameToCredentialEntryList.any { perNameEntryList ->
- perNameEntryList.sortedCredentialEntryList.any { entry ->
- entry.credentialType != CredentialType.PASSWORD &&
- entry.credentialType != CredentialType.PASSKEY
- }
+ if (areAllPasswordsOnPrimaryScreen)
+ R.string.get_dialog_title_choose_password_for
+ else if (areAllPasskeysOnPrimaryScreen)
+ R.string.get_dialog_title_choose_passkey_for
+ else if (primaryPageLockedEntryList.isNotEmpty() ||
+ primaryPageCredentialEntryList.any {
+ it.sortedCredentialEntryList.first().credentialType !=
+ CredentialType.PASSWORD &&
+ it.sortedCredentialEntryList.first().credentialType !=
+ CredentialType.PASSKEY
}
- ) // For an unknown / locked entry, it's not true that it is
+ ) // An unknown typed / locked entry exists, and we can't say it is
// already saved, strictly speaking. Hence use a different title
- // without the mention of "saved"
+ // without the mention of "saved".
R.string.get_dialog_title_choose_sign_in_for
- else
+ else // All entries on the primary screen are passkeys or passwords
R.string.get_dialog_title_choose_saved_sign_in_for
},
requestDisplayInfo.appName
@@ -490,8 +499,11 @@
showMoreForTruncatedEntry.value = it.hasVisualOverflow
},
hasSingleEntry = hasSingleEntry,
+ hasSingleProvider = singleProviderId != null,
shouldOverrideIcon = entry.isDefaultIconPreferredAsSingleProvider &&
(singleProviderId != null),
+ shouldRemoveTypeDisplayName = areAllPasswordsOnPrimaryScreen ||
+ areAllPasskeysOnPrimaryScreen
)
}
primaryPageLockedEntryList.forEach {
@@ -754,11 +766,19 @@
enforceOneLine: Boolean = false,
onTextLayout: (TextLayoutResult) -> Unit = {},
// Make optional since the secondary page doesn't care about this value.
- hasSingleEntry: Boolean? = null,
+ hasSingleEntry: Boolean = false,
// For primary page only, if all display entries come from the same provider AND if that
// provider has opted in via isDefaultIconPreferredAsSingleProvider, then we override the
// display icon to the default icon for the given credential type.
shouldOverrideIcon: Boolean = false,
+ // For primary page only, if all entries come from the same provider, then remove that provider
+ // name from each entry, since that provider icon + name will be shown front and central at
+ // the top of the bottom sheet.
+ hasSingleProvider: Boolean = false,
+ // For primary page only, if all visible entrise are of the same type and that type is passkey
+ // or password, then set this bit to true to remove the type display name from each entry for
+ // simplification, since that info is mentioned in the title.
+ shouldRemoveTypeDisplayName: Boolean = false,
) {
val (username, displayName) = if (credentialEntryInfo.credentialType == CredentialType.PASSKEY)
userAndDisplayNameForPasskey(
@@ -788,17 +808,16 @@
entryHeadlineText = username,
entrySecondLineText = displayName,
entryThirdLineText =
- (if (hasSingleEntry != null && hasSingleEntry)
- if (credentialEntryInfo.credentialType == CredentialType.PASSKEY ||
- credentialEntryInfo.credentialType == CredentialType.PASSWORD)
- emptyList()
+ (if (hasSingleEntry)
+ if (shouldRemoveTypeDisplayName) emptyList()
// Still show the type display name for all non-password/passkey types since it won't be
// mentioned in the bottom sheet heading.
else listOf(credentialEntryInfo.credentialTypeDisplayName)
else listOf(
- credentialEntryInfo.credentialTypeDisplayName,
- credentialEntryInfo.providerDisplayName
- )).filterNot(TextUtils::isEmpty).let { itemsToDisplay ->
+ if (shouldRemoveTypeDisplayName) null
+ else credentialEntryInfo.credentialTypeDisplayName,
+ if (hasSingleProvider) null else credentialEntryInfo.providerDisplayName
+ )).filterNot{ it.isNullOrBlank() }.let { itemsToDisplay ->
if (itemsToDisplay.isEmpty()) null
else itemsToDisplay.joinToString(
separator = stringResource(R.string.get_dialog_sign_in_type_username_separator)
diff --git a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenImagePathManager.kt b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenPathManager.kt
similarity index 77%
rename from packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenImagePathManager.kt
rename to packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenPathManager.kt
index 6aef24d..9cfdffd 100644
--- a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenImagePathManager.kt
+++ b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenPathManager.kt
@@ -18,34 +18,34 @@
import android.os.Build
import androidx.test.platform.app.InstrumentationRegistry
-import platform.test.screenshot.GoldenImagePathManager
+import platform.test.screenshot.GoldenPathManager
import platform.test.screenshot.PathConfig
/** The assets path to be used by all CredentialManager screenshot tests. */
private const val ASSETS_PREFIX = "frameworks/base/packages/CredentialManager"
private const val ASSETS_PATH = "${ASSETS_PREFIX}/tests/robotests/screenshot/customization/assets"
-private const val ASSETS_PATH_ROBO =
- "${ASSETS_PREFIX}/tests/robotests/customization/assets"
+private const val ASSETS_PATH_ROBO = "${ASSETS_PREFIX}/tests/robotests/customization/assets"
private val isRobolectric = Build.FINGERPRINT.contains("robolectric")
-class CredentialManagerGoldenImagePathManager(
- pathConfig: PathConfig,
- assetsPathRelativeToBuildRoot: String = if (isRobolectric) ASSETS_PATH_ROBO else ASSETS_PATH
-) : GoldenImagePathManager(
+class CredentialManagerGoldenPathManager(
+ pathConfig: PathConfig,
+ assetsPathRelativeToBuildRoot: String = if (isRobolectric) ASSETS_PATH_ROBO else ASSETS_PATH
+) :
+ GoldenPathManager(
appContext = InstrumentationRegistry.getInstrumentation().context,
assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
deviceLocalPath =
- InstrumentationRegistry.getInstrumentation()
+ InstrumentationRegistry.getInstrumentation()
.targetContext
.filesDir
.absolutePath
.toString() + "/credman_screenshots",
pathConfig = pathConfig,
-) {
+ ) {
override fun toString(): String {
// This string is appended to all actual/expected screenshots on the device, so make sure
// it is a static value.
- return "CredentialManagerGoldenImagePathManager"
+ return "CredentialManagerGoldenPathManager"
}
-}
\ No newline at end of file
+}
diff --git a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
index 28d83ee..b843213 100644
--- a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
+++ b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
@@ -60,7 +60,7 @@
@get:Rule
val screenshotRule = ComposeScreenshotTestRule(
emulationSpec,
- CredentialManagerGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
+ CredentialManagerGoldenPathManager(getEmulatedDevicePathConfig(emulationSpec))
)
@get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule()
diff --git a/packages/CredentialManager/wear/res/values/strings.xml b/packages/CredentialManager/wear/res/values/strings.xml
index 9480e64..ee8bb78 100644
--- a/packages/CredentialManager/wear/res/values/strings.xml
+++ b/packages/CredentialManager/wear/res/values/strings.xml
@@ -21,9 +21,6 @@
<!-- Title of a screen prompting if the user would like to use their saved passkey.
[CHAR LIMIT=80] -->
<string name="use_passkey_title">Use passkey?</string>
- <!-- Title of a screen prompting if the user would like to use their saved passkey.
-[CHAR LIMIT=80] -->
- <string name="use_sign_in_with_provider_title">Use your sign in for %1$s</string>
<!-- Title of a screen prompting if the user would like to sign in with provider
[CHAR LIMIT=80] -->
<string name="use_password_title">Use password?</string>
@@ -35,6 +32,8 @@
<string name="dialog_sign_in_options_button">Sign-in Options</string>
<!-- Title for multiple credentials folded screen. [CHAR LIMIT=NONE] -->
<string name="sign_in_options_title">Sign-in Options</string>
+ <!-- Provider settings list title. [CHAR LIMIT=NONE] -->
+ <string name="provider_list_title">Manage sign-ins</string>
<!-- Title for multiple credentials screen. [CHAR LIMIT=NONE] -->
<string name="choose_sign_in_title">Choose a sign in</string>
<!-- Title for multiple credentials screen with only passkeys. [CHAR LIMIT=NONE] -->
diff --git a/packages/CredentialManager/wear/robotests/Android.bp b/packages/CredentialManager/wear/robotests/Android.bp
new file mode 100644
index 0000000..c0a1822
--- /dev/null
+++ b/packages/CredentialManager/wear/robotests/Android.bp
@@ -0,0 +1,28 @@
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_robolectric_test {
+ name: "CredentialSelectorTests",
+ srcs: [
+ "src/**/*.kt",
+ ],
+ // Include test libraries.
+ instrumentation_for: "ClockworkCredentialManager",
+ libs: [
+ "androidx.test.runner",
+ "androidx.test.ext.junit",
+ "kotlinx_coroutines_android",
+ "kotlinx_coroutines",
+ "kotlinx-coroutines-core",
+ "kotlinx_coroutines_test",
+ "mockito-robolectric-prebuilt",
+ "mockito-kotlin2",
+ "CredentialManagerShared",
+ "ClockworkCredentialManager",
+ "framework_graphics_flags_java_lib",
+ ],
+ java_resource_dirs: ["config"],
+ upstream: true,
+}
diff --git a/packages/CredentialManager/wear/robotests/config/robolectric.properties b/packages/CredentialManager/wear/robotests/config/robolectric.properties
new file mode 100644
index 0000000..140e42b
--- /dev/null
+++ b/packages/CredentialManager/wear/robotests/config/robolectric.properties
@@ -0,0 +1,16 @@
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+sdk=NEWEST_SDK
+
diff --git a/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt
new file mode 100644
index 0000000..3422d3d
--- /dev/null
+++ b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.credentialmanager
+
+import java.time.Instant
+import android.graphics.drawable.Drawable
+import com.android.credentialmanager.model.get.CredentialEntryInfo
+import com.android.credentialmanager.model.get.ActionEntryInfo
+import com.android.credentialmanager.model.get.AuthenticationEntryInfo
+import com.android.credentialmanager.model.Request
+import androidx.test.filters.SmallTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.mockito.kotlin.mock
+import org.junit.runner.RunWith
+import com.android.credentialmanager.model.CredentialType
+import com.google.common.truth.Truth.assertThat
+import com.android.credentialmanager.ui.mappers.toGet
+import com.android.credentialmanager.model.get.ProviderInfo
+import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry.PerUserNameEntries
+
+/** Unit tests for [CredentialSelectorUiStateGetMapper]. */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CredentialSelectorUiStateGetMapperTest {
+
+ private val mDrawable = mock<Drawable>()
+
+ private val actionEntryInfo =
+ ActionEntryInfo(
+ providerId = "",
+ entryKey = "",
+ entrySubkey = "",
+ pendingIntent = null,
+ fillInIntent = null,
+ title = "title",
+ icon = mDrawable,
+ subTitle = "subtitle",
+ )
+
+ private val authenticationEntryInfo =
+ AuthenticationEntryInfo(
+ providerId = "",
+ entryKey = "",
+ entrySubkey = "",
+ pendingIntent = null,
+ fillInIntent = null,
+ title = "title",
+ providerDisplayName = "",
+ icon = mDrawable,
+ isUnlockedAndEmpty = true,
+ isLastUnlocked = true
+ )
+
+ val passkeyCredentialEntryInfo =
+ createCredentialEntryInfo(credentialType = CredentialType.PASSKEY, userName = "userName")
+
+ val unknownCredentialEntryInfo =
+ createCredentialEntryInfo(credentialType = CredentialType.UNKNOWN, userName = "userName2")
+
+ val passwordCredentialEntryInfo =
+ createCredentialEntryInfo(credentialType = CredentialType.PASSWORD, userName = "userName")
+
+ val recentlyUsedPasskeyCredential =
+ createCredentialEntryInfo(credentialType =
+ CredentialType.PASSKEY, lastUsedTimeMillis = 2L, userName = "userName")
+
+ val recentlyUsedPasswordCredential =
+ createCredentialEntryInfo(credentialType =
+ CredentialType.PASSWORD, lastUsedTimeMillis = 2L, userName = "userName")
+
+ val credentialList1 = listOf(
+ passkeyCredentialEntryInfo,
+ passwordCredentialEntryInfo
+ )
+
+ val credentialList2 = listOf(
+ passkeyCredentialEntryInfo,
+ passwordCredentialEntryInfo,
+ recentlyUsedPasskeyCredential,
+ unknownCredentialEntryInfo,
+ recentlyUsedPasswordCredential
+ )
+
+ @Test
+ fun `On primary screen, just one account returns SingleEntry`() {
+ val getCredentialUiState = Request.Get(
+ token = null,
+ resultReceiver = null,
+ finalResponseReceiver = null,
+ providerInfos = listOf(createProviderInfo(credentialList1))).toGet(isPrimary = true)
+
+ assertThat(getCredentialUiState).isEqualTo(
+ CredentialSelectorUiState.Get.SingleEntry(passkeyCredentialEntryInfo)
+ ) // prefer passkey over password for selected credential
+ }
+
+ @Test
+ fun `On primary screen, multiple accounts returns SingleEntryPerAccount`() {
+ val getCredentialUiState = Request.Get(
+ token = null,
+ resultReceiver = null,
+ finalResponseReceiver = null,
+ providerInfos = listOf(createProviderInfo(listOf(passkeyCredentialEntryInfo,
+ unknownCredentialEntryInfo)))).toGet(isPrimary = true)
+
+ assertThat(getCredentialUiState).isEqualTo(
+ CredentialSelectorUiState.Get.SingleEntryPerAccount(
+ sortedEntries = listOf(
+ passkeyCredentialEntryInfo, // userName
+ unknownCredentialEntryInfo // userName2
+ ),
+ authenticationEntryList = listOf(authenticationEntryInfo)
+ )) // prefer passkey from account 1, then unknown from account 2
+ }
+
+ @Test
+ fun `On secondary screen, a MultipleEntry is returned`() {
+ val getCredentialUiState = Request.Get(
+ token = null,
+ resultReceiver = null,
+ finalResponseReceiver = null,
+ providerInfos = listOf(createProviderInfo(credentialList1))).toGet(isPrimary = false)
+
+ assertThat(getCredentialUiState).isEqualTo(
+ CredentialSelectorUiState.Get.MultipleEntry(
+ listOf(PerUserNameEntries("userName", listOf(
+ passkeyCredentialEntryInfo,
+ passwordCredentialEntryInfo))
+ ),
+ listOf(actionEntryInfo),
+ listOf(authenticationEntryInfo)
+ ))
+ }
+
+ @Test
+ fun `Returned multiple entry is sorted by credentialType and lastUsedTimeMillis`() {
+ val getCredentialUiState = Request.Get(
+ token = null,
+ resultReceiver = null,
+ finalResponseReceiver = null,
+ providerInfos = listOf(createProviderInfo(credentialList1),
+ createProviderInfo(credentialList2))).toGet(isPrimary = false)
+
+ assertThat(getCredentialUiState).isEqualTo(
+ CredentialSelectorUiState.Get.MultipleEntry(
+ listOf(
+ PerUserNameEntries("userName",
+ listOf(
+ recentlyUsedPasskeyCredential, // from provider 2
+ passkeyCredentialEntryInfo, // from provider 1 or 2
+ passkeyCredentialEntryInfo, // from provider 1 or 2
+ recentlyUsedPasswordCredential, // from provider 2
+ passwordCredentialEntryInfo, // from provider 1 or 2
+ passwordCredentialEntryInfo, // from provider 1 or 2
+ )),
+ PerUserNameEntries("userName2", listOf(unknownCredentialEntryInfo)),
+ ),
+ listOf(actionEntryInfo, actionEntryInfo),
+ listOf(authenticationEntryInfo, authenticationEntryInfo)
+ )
+ )
+ }
+
+ fun createCredentialEntryInfo(
+ userName: String,
+ credentialType: CredentialType = CredentialType.PASSKEY,
+ lastUsedTimeMillis: Long = 0L
+ ): CredentialEntryInfo =
+ CredentialEntryInfo(
+ providerId = "",
+ entryKey = "",
+ entrySubkey = "",
+ pendingIntent = null,
+ fillInIntent = null,
+ credentialType = credentialType,
+ rawCredentialType = "",
+ credentialTypeDisplayName = "",
+ providerDisplayName = "",
+ userName = userName,
+ displayName = "",
+ icon = mDrawable,
+ shouldTintIcon = false,
+ lastUsedTimeMillis = Instant.ofEpochMilli(lastUsedTimeMillis),
+ isAutoSelectable = true,
+ entryGroupId = "",
+ isDefaultIconPreferredAsSingleProvider = false,
+ affiliatedDomain = "",
+ )
+
+ fun createProviderInfo(credentials: List<CredentialEntryInfo> = listOf()): ProviderInfo =
+ ProviderInfo(
+ id = "providerInfo",
+ icon = mDrawable,
+ displayName = "displayName",
+ credentialEntryList = credentials,
+ authenticationEntryList = listOf(authenticationEntryInfo),
+ remoteEntry = null,
+ actionEntryList = listOf(actionEntryInfo)
+ )
+}
diff --git a/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorViewModelTest.kt b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorViewModelTest.kt
new file mode 100644
index 0000000..b79f34c
--- /dev/null
+++ b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorViewModelTest.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.credentialmanager
+
+import org.mockito.kotlin.whenever
+import com.android.credentialmanager.model.EntryInfo
+import com.android.credentialmanager.model.Request
+import androidx.test.filters.SmallTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.Before
+import java.util.Collections.emptyList
+import org.junit.runner.RunWith
+import android.content.Intent
+import com.android.credentialmanager.client.CredentialManagerClient
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.launchIn
+import android.credentials.selection.BaseDialogResult
+import com.google.common.truth.Truth.assertThat
+import org.mockito.kotlin.doReturn
+import kotlinx.coroutines.Job
+import org.junit.After
+import org.robolectric.shadows.ShadowLooper
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** Unit tests for [CredentialSelectorViewModel]. */
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CredentialSelectorViewModelTest {
+ private val testScope = TestScope(UnconfinedTestDispatcher())
+
+ private val stateFlow: MutableStateFlow<Request?> = MutableStateFlow(Request.Create(null))
+ private val credentialManagerClient = mock<CredentialManagerClient>{
+ on { requests } doReturn stateFlow
+ }
+ private val mViewModel = CredentialSelectorViewModel(credentialManagerClient)
+ private lateinit var job: Job
+
+ val testEntryInfo =
+ EntryInfo(
+ providerId = "",
+ entryKey = "",
+ entrySubkey = "",
+ pendingIntent = null,
+ fillInIntent = null,
+ shouldTerminateUiUponSuccessfulProviderResult = true)
+
+ @Before
+ fun setUp() {
+ job = checkNotNull(mViewModel).uiState.launchIn(testScope)
+ }
+
+ @After
+ fun teardown() {
+ job.cancel()
+ }
+
+ @Test
+ fun `Setting state to idle when receiving null request`() {
+ stateFlow.value = null
+ ShadowLooper.idleMainLooper()
+
+ assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Idle)
+ }
+
+ @Test
+ fun `Setting state to cancel when receiving Cancel request`() {
+ stateFlow.value = Request.Cancel(appName = "appName", token = null)
+ ShadowLooper.idleMainLooper()
+
+ assertThat(mViewModel.uiState.value)
+ .isEqualTo(CredentialSelectorUiState.Cancel("appName"))
+ }
+
+ @Test
+ fun `Setting state to create when receiving Create request`() {
+ stateFlow.value = Request.Create(token = null)
+ ShadowLooper.idleMainLooper()
+
+ assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Create)
+ }
+
+ @Test
+ fun `Closing app when receiving Close request`() {
+ stateFlow.value = Request.Close(token = null)
+ ShadowLooper.idleMainLooper()
+
+ assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+ }
+
+ @Test
+ fun `Updates request`() {
+ val intent = Intent()
+
+ mViewModel.updateRequest(intent)
+
+ verify(credentialManagerClient).updateRequest(intent)
+ }
+
+ @Test
+ fun `Back on a single entry screen closes app`() {
+ mViewModel.openSecondaryScreen()
+ stateFlow.value = Request.Get(
+ token = null,
+ resultReceiver = null,
+ finalResponseReceiver = null,
+ providerInfos = emptyList())
+
+ mViewModel.back()
+ ShadowLooper.idleMainLooper()
+
+ assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+ }
+
+ @Test
+ fun `Back on a multiple entry screen gets us back to a primary screen`() {
+ mViewModel.openSecondaryScreen()
+ stateFlow.value = Request.Get(
+ token = null,
+ resultReceiver = null,
+ finalResponseReceiver = null,
+ providerInfos = emptyList())
+
+ mViewModel.back()
+ ShadowLooper.idleMainLooper()
+
+ assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+ }
+
+ @Test
+ fun `Back on create request state closes app`() {
+ stateFlow.value = Request.Create(token = null)
+
+ mViewModel.back()
+ ShadowLooper.idleMainLooper()
+
+ assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+ }
+
+ @Test
+ fun `Back on close request state closes app`() {
+ stateFlow.value = Request.Close(token = null)
+
+ mViewModel.back()
+ ShadowLooper.idleMainLooper()
+
+ assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+ }
+
+ @Test
+ fun `Back on cancel request state closes app`() {
+ stateFlow.value = Request.Cancel(appName = "", token = null)
+
+ mViewModel.back()
+ ShadowLooper.idleMainLooper()
+
+ assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+ }
+
+ @Test
+ fun `Back on idle request state closes app`() {
+ stateFlow.value = null
+
+ mViewModel.back()
+ ShadowLooper.idleMainLooper()
+
+ assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+ }
+
+ @Test
+ fun `Cancel closes the app`() {
+ mViewModel.cancel()
+ ShadowLooper.idleMainLooper()
+
+ verify(credentialManagerClient).sendError(BaseDialogResult.RESULT_CODE_DIALOG_USER_CANCELED)
+ assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+ }
+
+ @Test
+ fun `Send entry selection result closes app and calls client method`() {
+ whenever(credentialManagerClient.sendEntrySelectionResult(
+ entryInfo = testEntryInfo,
+ resultCode = null,
+ resultData = null,
+ isAutoSelected = false
+ )).thenReturn(true)
+
+ mViewModel.sendSelectionResult(
+ entryInfo = testEntryInfo,
+ resultCode = null,
+ resultData = null,
+ isAutoSelected = false)
+ ShadowLooper.idleMainLooper()
+
+ verify(credentialManagerClient).sendEntrySelectionResult(
+ testEntryInfo, null, null, false
+ )
+ assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Close)
+ }
+
+ @Test
+ fun `Send entry selection result does not close app on false return`() {
+ whenever(credentialManagerClient.sendEntrySelectionResult(
+ entryInfo = testEntryInfo,
+ resultCode = null,
+ resultData = null,
+ isAutoSelected = false
+ )).thenReturn(false)
+ stateFlow.value = Request.Create(null)
+
+ mViewModel.sendSelectionResult(entryInfo = testEntryInfo, resultCode = null,
+ resultData = null, isAutoSelected = false)
+ ShadowLooper.idleMainLooper()
+
+ verify(credentialManagerClient).sendEntrySelectionResult(
+ entryInfo = testEntryInfo,
+ resultCode = null,
+ resultData = null,
+ isAutoSelected = false
+ )
+ assertThat(mViewModel.uiState.value).isEqualTo(CredentialSelectorUiState.Create)
+ }
+}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 66be7ba..9d97763 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -59,8 +59,10 @@
isPrimaryScreen,
shouldClose
) { request, isPrimary, shouldClose ->
+ Log.d(TAG, "Request updated: " + request?.toString() +
+ " isClose: " + shouldClose.toString() +
+ " isPrimaryScreen: " + isPrimary.toString())
if (shouldClose) {
- Log.d(TAG, "Request finished, closing ")
return@combine Close
}
@@ -139,7 +141,10 @@
data object Idle : CredentialSelectorUiState()
sealed class Get : CredentialSelectorUiState() {
data class SingleEntry(val entry: CredentialEntryInfo) : Get()
- data class SingleEntryPerAccount(val sortedEntries: List<CredentialEntryInfo>) : Get()
+ data class SingleEntryPerAccount(
+ val sortedEntries: List<CredentialEntryInfo>,
+ val authenticationEntryList: List<AuthenticationEntryInfo>,
+ ) : Get()
data class MultipleEntry(
val accounts: List<PerUserNameEntries>,
val actionEntryList: List<ActionEntryInfo>,
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
index 405de1d3..bf4c988 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
@@ -29,6 +29,7 @@
import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
import androidx.wear.compose.navigation.rememberSwipeDismissableNavHostState
import com.android.credentialmanager.CredentialSelectorUiState
+import com.android.credentialmanager.CredentialSelectorUiState.Get.SingleEntryPerAccount
import com.android.credentialmanager.CredentialSelectorUiState.Get.SingleEntry
import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry
import com.android.credentialmanager.CredentialSelectorViewModel
@@ -45,6 +46,8 @@
import com.android.credentialmanager.model.CredentialType
import com.android.credentialmanager.model.EntryInfo
import com.android.credentialmanager.ui.screens.multiple.MultiCredentialsFoldScreen
+import com.android.credentialmanager.ui.screens.multiple.MultiCredentialsFlattenScreen
+
@OptIn(ExperimentalHorologistApi::class)
@Composable
@@ -78,59 +81,70 @@
scrollable(Screen.SinglePasskeyScreen.route) {
SinglePasskeyScreen(
- credentialSelectorUiState = viewModel.uiState.value as SingleEntry,
+ entry = (remember { uiState } as SingleEntry).entry,
columnState = it.columnState,
+ flowEngine = flowEngine,
)
}
scrollable(Screen.SignInWithProviderScreen.route) {
SignInWithProviderScreen(
- credentialSelectorUiState = viewModel.uiState.value as SingleEntry,
+ entry = (remember { uiState } as SingleEntry).entry,
columnState = it.columnState,
+ flowEngine = flowEngine,
)
}
scrollable(Screen.MultipleCredentialsScreenFold.route) {
MultiCredentialsFoldScreen(
- credentialSelectorUiState = viewModel.uiState.value as MultipleEntry,
- screenIcon = null,
+ credentialSelectorUiState = (remember { uiState } as SingleEntryPerAccount),
columnState = it.columnState,
+ flowEngine = flowEngine,
+ )
+ }
+
+ scrollable(Screen.MultipleCredentialsScreenFlatten.route) {
+ MultiCredentialsFlattenScreen(
+ credentialSelectorUiState = (remember { uiState } as MultipleEntry),
+ columnState = it.columnState,
+ flowEngine = flowEngine,
)
}
}
- BackHandler(true) {
- viewModel.back()
- }
- Log.d(TAG, "uiState change, state: $uiState")
- when (val state = uiState) {
- CredentialSelectorUiState.Idle -> {
- if (navController.currentDestination?.route != Screen.Loading.route) {
- navController.navigateToLoading()
+ BackHandler(true) {
+ viewModel.back()
+ }
+ Log.d(TAG, "uiState change, state: $uiState")
+ when (val state = uiState) {
+ CredentialSelectorUiState.Idle -> {
+ if (navController.currentDestination?.route != Screen.Loading.route) {
+ navController.navigateToLoading()
+ }
+ }
+
+ is CredentialSelectorUiState.Get -> {
+ handleGetNavigation(
+ navController = navController,
+ state = state,
+ onCloseApp = onCloseApp,
+ selectEntry = selectEntry
+ )
+ }
+
+ CredentialSelectorUiState.Create -> {
+ // TODO: b/301206624 - Implement create flow
+ onCloseApp()
+ }
+
+ is CredentialSelectorUiState.Cancel -> {
+ onCloseApp()
+ }
+
+ CredentialSelectorUiState.Close -> {
+ onCloseApp()
}
}
- is CredentialSelectorUiState.Get -> {
- handleGetNavigation(
- navController = navController,
- state = state,
- onCloseApp = onCloseApp,
- selectEntry = selectEntry
- )
- }
-
- CredentialSelectorUiState.Create -> {
- // TODO: b/301206624 - Implement create flow
- onCloseApp()
- }
-
- is CredentialSelectorUiState.Cancel -> {
- onCloseApp()
- }
-
- CredentialSelectorUiState.Close -> {
- onCloseApp()
- }
}
-}
private fun handleGetNavigation(
navController: NavController,
@@ -157,13 +171,12 @@
}
}
- is MultipleEntry -> {
- navController.navigateToMultipleCredentialsFoldScreen()
- }
+ is SingleEntryPerAccount -> {
+ navController.navigateToMultipleCredentialsFoldScreen()
+ }
- else -> {
- // TODO: b/301206470 - Implement other get flows
- onCloseApp()
+ is MultipleEntry -> {
+ navController.navigateToMultipleCredentialsFlattenScreen()
+ }
}
}
-}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
index 03b0931..7a936b6 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
@@ -32,11 +32,14 @@
return if (isPrimary) {
if (accounts.size == 1) {
CredentialSelectorUiState.Get.SingleEntry(
- accounts[0].value.minWith(comparator)
+ entry = accounts[0].value.minWith(comparator)
)
} else {
CredentialSelectorUiState.Get.SingleEntryPerAccount(
- accounts.map { it.value.minWith(comparator) }.sortedWith(comparator)
+ sortedEntries = accounts.map {
+ it.value.minWith(comparator)
+ }.sortedWith(comparator),
+ authenticationEntryList = providerInfos.flatMap { it.authenticationEntryList }
)
}
} else {
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
index 11188b4..d54103c 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenScreen.kt
@@ -15,112 +15,53 @@
*/
package com.android.credentialmanager.ui.screens.multiple
-import android.graphics.drawable.Drawable
-import com.android.credentialmanager.ui.screens.UiState
-import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.navigation.NavHostController
-import androidx.navigation.compose.rememberNavController
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.Text
import com.android.credentialmanager.ui.components.SignInHeader
import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry
+import com.android.credentialmanager.FlowEngine
import com.android.credentialmanager.R
-import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
-import com.android.credentialmanager.model.get.ActionEntryInfo
import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.ui.components.CredentialsScreenChip
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.ScalingLazyColumn
import com.google.android.horologist.compose.layout.ScalingLazyColumnState
-
/**
* Screen that shows multiple credentials to select from, grouped by accounts
*
* @param credentialSelectorUiState The app bar view model.
- * @param screenIcon The view model corresponding to the home page.
* @param columnState ScalingLazyColumn configuration to be be applied
* @param modifier styling for composable
- * @param viewModel ViewModel that updates ui state for this screen
- * @param navController handles navigation events from this screen
+ * @param flowEngine [FlowEngine] that updates ui state for this screen
*/
@OptIn(ExperimentalHorologistApi::class)
@Composable
fun MultiCredentialsFlattenScreen(
credentialSelectorUiState: MultipleEntry,
- screenIcon: Drawable?,
columnState: ScalingLazyColumnState,
- modifier: Modifier = Modifier,
- viewModel: MultiCredentialsFlattenViewModel = hiltViewModel(),
- navController: NavHostController = rememberNavController(),
+ flowEngine: FlowEngine,
) {
- val uiState by viewModel.uiState.collectAsStateWithLifecycle()
-
- when (val state = uiState) {
- UiState.CredentialScreen -> {
- MultiCredentialsFlattenScreen(
- state = credentialSelectorUiState,
- columnState = columnState,
- screenIcon = screenIcon,
- onActionEntryClicked = viewModel::onActionEntryClicked,
- onCredentialClicked = viewModel::onCredentialClicked,
- modifier = modifier,
- )
- }
-
- is UiState.CredentialSelected -> {
- val launcher = rememberLauncherForActivityResult(
- StartBalIntentSenderForResultContract()
- ) {
- viewModel.onInfoRetrieved(it.resultCode, null)
- }
-
- SideEffect {
- state.intentSenderRequest?.let {
- launcher.launch(it)
- }
- }
- }
-
- UiState.Cancel -> {
- navController.popBackStack()
- }
- }
-}
-
-@OptIn(ExperimentalHorologistApi::class)
-@Composable
-fun MultiCredentialsFlattenScreen(
- state: MultipleEntry,
- columnState: ScalingLazyColumnState,
- screenIcon: Drawable?,
- onActionEntryClicked: (entryInfo: ActionEntryInfo) -> Unit,
- onCredentialClicked: (entryInfo: CredentialEntryInfo) -> Unit,
- modifier: Modifier,
-) {
+ val selectEntry = flowEngine.getEntrySelector()
ScalingLazyColumn(
columnState = columnState,
- modifier = modifier.fillMaxSize(),
+ modifier = Modifier.fillMaxSize(),
) {
item {
// make this credential specific if all credentials are same
SignInHeader(
- icon = screenIcon,
+ icon = null,
title = stringResource(R.string.sign_in_options_title),
)
}
- state.accounts.forEach { userNameEntries ->
+ credentialSelectorUiState.accounts.forEach { userNameEntries ->
item {
Text(
text = userNameEntries.userName,
@@ -135,17 +76,16 @@
item {
CredentialsScreenChip(
label = credential.userName,
- onClick = { onCredentialClicked(credential) },
- secondaryLabel = credential.userName,
+ onClick = { selectEntry(credential, false) },
+ secondaryLabel = credential.credentialTypeDisplayName,
icon = credential.icon,
- modifier = modifier,
)
}
}
}
item {
Text(
- text = "Manage Sign-ins",
+ text = stringResource(R.string.provider_list_title),
modifier = Modifier
.padding(top = 6.dp)
.padding(horizontal = 10.dp),
@@ -153,14 +93,13 @@
)
}
- state.actionEntryList.forEach {
+ credentialSelectorUiState.actionEntryList.forEach {actionEntry ->
item {
CredentialsScreenChip(
- label = it.title,
- onClick = { onActionEntryClicked(it) },
+ label = actionEntry.title,
+ onClick = { selectEntry(actionEntry, false) },
secondaryLabel = null,
- icon = it.icon,
- modifier = modifier,
+ icon = actionEntry.icon,
)
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenViewModel.kt
deleted file mode 100644
index ee5f3f4..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFlattenViewModel.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.screens.multiple
-
-import android.content.Intent
-import android.credentials.selection.ProviderPendingIntentResponse
-import android.credentials.selection.UserSelectionDialogResult
-import androidx.lifecycle.ViewModel
-import com.android.credentialmanager.client.CredentialManagerClient
-import com.android.credentialmanager.ktx.getIntentSenderRequest
-import com.android.credentialmanager.model.Request
-import com.android.credentialmanager.model.get.ActionEntryInfo
-import com.android.credentialmanager.model.get.CredentialEntryInfo
-import com.android.credentialmanager.ui.screens.UiState
-import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import javax.inject.Inject
-
-/** ViewModel for [MultiCredentialsFlattenScreen].*/
-@HiltViewModel
-class MultiCredentialsFlattenViewModel @Inject constructor(
- private val credentialManagerClient: CredentialManagerClient,
-) : ViewModel() {
-
- private lateinit var requestGet: Request.Get
- private lateinit var entryInfo: CredentialEntryInfo
-
- private val _uiState =
- MutableStateFlow<UiState>(UiState.CredentialScreen)
- val uiState: StateFlow<UiState> = _uiState
-
- fun onCredentialClicked(entryInfo: CredentialEntryInfo) {
- this.entryInfo = entryInfo
- _uiState.value = UiState.CredentialSelected(
- intentSenderRequest = entryInfo.getIntentSenderRequest()
- )
- }
-
- fun onCancelClicked() {
- _uiState.value = UiState.Cancel
- }
-
- fun onInfoRetrieved(
- resultCode: Int? = null,
- resultData: Intent? = null,
- ) {
- val userSelectionDialogResult = UserSelectionDialogResult(
- requestGet.token,
- entryInfo.providerId,
- entryInfo.entryKey,
- entryInfo.entrySubkey,
- if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
- )
- credentialManagerClient.sendResult(userSelectionDialogResult)
- }
-
- fun onActionEntryClicked(actionEntryInfo: ActionEntryInfo) {
- // TODO(b/322797032)to be filled out
- }
-}
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
index 5515c86..6f32c99 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
@@ -16,24 +16,15 @@
package com.android.credentialmanager.ui.screens.multiple
-import com.android.credentialmanager.ui.screens.UiState
-import android.graphics.drawable.Drawable
-import androidx.activity.compose.rememberLauncherForActivityResult
import com.android.credentialmanager.R
import androidx.compose.ui.res.stringResource
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.navigation.NavHostController
-import androidx.navigation.compose.rememberNavController
import com.android.credentialmanager.CredentialSelectorUiState
-import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
+import com.android.credentialmanager.FlowEngine
import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.ui.components.DismissChip
import com.android.credentialmanager.ui.components.CredentialsScreenChip
@@ -49,74 +40,22 @@
* Screen that shows multiple credentials to select from.
*
* @param credentialSelectorUiState The app bar view model.
- * @param screenIcon The view model corresponding to the home page.
* @param columnState ScalingLazyColumn configuration to be be applied
- * @param modifier styling for composable
- * @param viewModel ViewModel that updates ui state for this screen
- * @param navController handles navigation events from this screen
*/
@OptIn(ExperimentalHorologistApi::class)
@Composable
fun MultiCredentialsFoldScreen(
- credentialSelectorUiState: CredentialSelectorUiState.Get.MultipleEntry,
- screenIcon: Drawable?,
+ credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntryPerAccount,
columnState: ScalingLazyColumnState,
- modifier: Modifier = Modifier,
- viewModel: MultiCredentialsFoldViewModel = hiltViewModel(),
- navController: NavHostController = rememberNavController(),
+ flowEngine: FlowEngine,
) {
- val uiState by viewModel.uiState.collectAsStateWithLifecycle()
-
- when (val state = uiState) {
- UiState.CredentialScreen -> {
- MultiCredentialsFoldScreen(
- state = credentialSelectorUiState,
- onSignInOptionsClicked = viewModel::onSignInOptionsClicked,
- onCredentialClicked = viewModel::onCredentialClicked,
- onCancelClicked = viewModel::onCancelClicked,
- screenIcon = screenIcon,
- columnState = columnState,
- modifier = modifier
- )
- }
-
- is UiState.CredentialSelected -> {
- val launcher = rememberLauncherForActivityResult(
- StartBalIntentSenderForResultContract()
- ) {
- viewModel.onInfoRetrieved(it.resultCode, null)
- }
-
- SideEffect {
- state.intentSenderRequest?.let {
- launcher.launch(it)
- }
- }
- }
-
- UiState.Cancel -> {
- navController.popBackStack()
- }
- }
-}
-
-@OptIn(ExperimentalHorologistApi::class)
-@Composable
-fun MultiCredentialsFoldScreen(
- state: CredentialSelectorUiState.Get.MultipleEntry,
- onSignInOptionsClicked: () -> Unit,
- onCredentialClicked: (entryInfo: CredentialEntryInfo) -> Unit,
- onCancelClicked: () -> Unit,
- screenIcon: Drawable?,
- columnState: ScalingLazyColumnState,
- modifier: Modifier,
-) {
+ val selectEntry = flowEngine.getEntrySelector()
ScalingLazyColumn(
columnState = columnState,
- modifier = modifier.fillMaxSize(),
+ modifier = Modifier.fillMaxSize(),
) {
// flatten all credentials into one
- val credentials = state.accounts.flatMap { it.sortedCredentialEntryList }
+ val credentials = credentialSelectorUiState.sortedEntries
item {
var title = stringResource(R.string.choose_sign_in_title)
if (credentials.all{ it.credentialType == CredentialType.PASSKEY }) {
@@ -126,7 +65,7 @@
}
SignInHeader(
- icon = screenIcon,
+ icon = null,
title = title,
modifier = Modifier
.padding(top = 6.dp),
@@ -137,22 +76,24 @@
item {
CredentialsScreenChip(
label = credential.userName,
- onClick = { onCredentialClicked(credential) },
+ onClick = { selectEntry(credential, false) },
secondaryLabel = credential.credentialTypeDisplayName,
icon = credential.icon,
)
}
}
- state.authenticationEntryList.forEach { authenticationEntryInfo ->
+ credentialSelectorUiState.authenticationEntryList.forEach { authenticationEntryInfo ->
item {
LockedProviderChip(authenticationEntryInfo) {
- // TODO(b/322797032) invoke LockedProviderScreen here using flow engine
- }
+ selectEntry(authenticationEntryInfo, false) }
}
}
-
- item { SignInOptionsChip(onSignInOptionsClicked)}
- item { DismissChip(onCancelClicked) }
+ item {
+ SignInOptionsChip { flowEngine.openSecondaryScreen() }
+ }
+ item {
+ DismissChip { flowEngine.cancel() }
+ }
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldViewModel.kt
deleted file mode 100644
index 627a63d..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldViewModel.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.screens.multiple
-
-import android.content.Intent
-import android.credentials.selection.ProviderPendingIntentResponse
-import android.credentials.selection.UserSelectionDialogResult
-import androidx.lifecycle.ViewModel
-import com.android.credentialmanager.client.CredentialManagerClient
-import com.android.credentialmanager.ktx.getIntentSenderRequest
-import com.android.credentialmanager.model.Request
-import com.android.credentialmanager.model.get.CredentialEntryInfo
-import com.android.credentialmanager.ui.screens.UiState
-import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import javax.inject.Inject
-
-/** ViewModel for [MultiCredentialsFoldScreen].*/
-@HiltViewModel
-class MultiCredentialsFoldViewModel @Inject constructor(
- private val credentialManagerClient: CredentialManagerClient,
-) : ViewModel() {
-
- private lateinit var requestGet: Request.Get
- private lateinit var entryInfo: CredentialEntryInfo
-
- private val _uiState =
- MutableStateFlow<UiState>(UiState.CredentialScreen)
- val uiState: StateFlow<UiState> = _uiState
-
- fun onCredentialClicked(entryInfo: CredentialEntryInfo) {
- this.entryInfo = entryInfo
- _uiState.value = UiState.CredentialSelected(
- intentSenderRequest = entryInfo.getIntentSenderRequest()
- )
- }
-
- fun onSignInOptionsClicked() {
- // TODO(b/322797032) Implement navigation route for single credential screen to multiple
- // credentials
- }
-
- fun onCancelClicked() {
- _uiState.value = UiState.Cancel
- }
-
- fun onInfoRetrieved(
- resultCode: Int? = null,
- resultData: Intent? = null,
- ) {
- val userSelectionDialogResult = UserSelectionDialogResult(
- requestGet.token,
- entryInfo.providerId,
- entryInfo.entryKey,
- entryInfo.entrySubkey,
- if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
- )
- credentialManagerClient.sendResult(userSelectionDialogResult)
- }
-}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
index b2595a1..56b1c2e 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt
@@ -19,92 +19,38 @@
package com.android.credentialmanager.ui.screens.single.passkey
import androidx.compose.foundation.layout.Column
-import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.navigation.NavHostController
-import androidx.navigation.compose.rememberNavController
-import com.android.credentialmanager.CredentialSelectorUiState
+import com.android.credentialmanager.FlowEngine
import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.R
-import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
import com.android.credentialmanager.ui.components.AccountRow
import com.android.credentialmanager.ui.components.ContinueChip
import com.android.credentialmanager.ui.components.DismissChip
import com.android.credentialmanager.ui.components.SignInHeader
import com.android.credentialmanager.ui.components.SignInOptionsChip
import com.android.credentialmanager.ui.screens.single.SingleAccountScreen
-import com.android.credentialmanager.ui.screens.UiState
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.ScalingLazyColumnState
/**
* Screen that shows sign in with provider credential.
*
- * @param credentialSelectorUiState The app bar view model.
+ * @param entry The password entry
* @param columnState ScalingLazyColumn configuration to be be applied to SingleAccountScreen
* @param modifier styling for composable
- * @param viewModel ViewModel that updates ui state for this screen
- * @param navController handles navigation events from this screen
+ * @param flowEngine [FlowEngine] that updates ui state for this screen
*/
@OptIn(ExperimentalHorologistApi::class)
@Composable
fun SinglePasskeyScreen(
- credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntry,
- columnState: ScalingLazyColumnState,
- modifier: Modifier = Modifier,
- viewModel: SinglePasskeyScreenViewModel = hiltViewModel(),
- navController: NavHostController = rememberNavController(),
-) {
- viewModel.initialize(credentialSelectorUiState.entry)
-
- val uiState by viewModel.uiState.collectAsStateWithLifecycle()
-
- when (val state = uiState) {
- UiState.CredentialScreen -> {
- SinglePasskeyScreen(
- credentialSelectorUiState.entry,
- columnState,
- modifier,
- viewModel
- )
- }
-
- is UiState.CredentialSelected -> {
- val launcher = rememberLauncherForActivityResult(
- StartBalIntentSenderForResultContract()
- ) {
- viewModel.onPasskeyInfoRetrieved(it.resultCode, null)
- }
-
- SideEffect {
- state.intentSenderRequest?.let {
- launcher.launch(it)
- }
- }
- }
-
- UiState.Cancel -> {
- // TODO(b/322797032) add valid navigation path here for going back
- navController.popBackStack()
- }
- }
-}
-
-@OptIn(ExperimentalHorologistApi::class)
-@Composable
-fun SinglePasskeyScreen(
entry: CredentialEntryInfo,
columnState: ScalingLazyColumnState,
modifier: Modifier = Modifier,
- viewModel: SinglePasskeyScreenViewModel,
+ flowEngine: FlowEngine,
) {
SingleAccountScreen(
headerContent = {
@@ -114,27 +60,21 @@
)
},
accountContent = {
- if (entry.displayName != null) {
- AccountRow(
+ AccountRow(
primaryText = checkNotNull(entry.displayName),
secondaryText = entry.userName,
modifier = Modifier.padding(top = 10.dp),
)
- } else {
- AccountRow(
- primaryText = entry.userName,
- modifier = Modifier.padding(top = 10.dp),
- )
- }
},
columnState = columnState,
modifier = modifier.padding(horizontal = 10.dp)
) {
item {
+ val selectEntry = flowEngine.getEntrySelector()
Column {
- ContinueChip(viewModel::onContinueClick)
- SignInOptionsChip(viewModel::onSignInOptionsClick)
- DismissChip(viewModel::onDismissClick)
+ ContinueChip { selectEntry(entry, false) }
+ SignInOptionsChip{ flowEngine.openSecondaryScreen() }
+ DismissChip { flowEngine.cancel() }
}
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreenViewModel.kt
deleted file mode 100644
index 37ffaca..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreenViewModel.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.screens.single.passkey
-
-import android.content.Intent
-import android.credentials.selection.UserSelectionDialogResult
-import android.credentials.selection.ProviderPendingIntentResponse
-import androidx.annotation.MainThread
-import androidx.lifecycle.ViewModel
-import com.android.credentialmanager.ktx.getIntentSenderRequest
-import com.android.credentialmanager.model.Request
-import com.android.credentialmanager.client.CredentialManagerClient
-import com.android.credentialmanager.model.get.CredentialEntryInfo
-import dagger.hilt.android.lifecycle.HiltViewModel
-import com.android.credentialmanager.ui.screens.UiState
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import javax.inject.Inject
-
-@HiltViewModel
-class SinglePasskeyScreenViewModel @Inject constructor(
- private val credentialManagerClient: CredentialManagerClient,
-) : ViewModel() {
-
- private val _uiState =
- MutableStateFlow<UiState>(UiState.CredentialScreen)
- val uiState: StateFlow<UiState> = _uiState
-
- private lateinit var requestGet: Request.Get
- private lateinit var entryInfo: CredentialEntryInfo
-
-
- @MainThread
- fun initialize(entry: CredentialEntryInfo) {
- this.entryInfo = entry
- }
-
- fun onDismissClick() {
- _uiState.value = UiState.Cancel
- }
-
- fun onContinueClick() {
- _uiState.value = UiState.CredentialSelected(
- intentSenderRequest = entryInfo.getIntentSenderRequest()
- )
- }
-
- fun onSignInOptionsClick() {
- }
-
- fun onPasskeyInfoRetrieved(
- resultCode: Int? = null,
- resultData: Intent? = null,
- ) {
- val userSelectionDialogResult = UserSelectionDialogResult(
- requestGet.token,
- entryInfo.providerId,
- entryInfo.entryKey,
- entryInfo.entrySubkey,
- if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
- )
- credentialManagerClient.sendResult(userSelectionDialogResult)
- }
-}
-
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt
index b0ece0d..3a86feb 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderScreen.kt
@@ -16,100 +16,43 @@
package com.android.credentialmanager.ui.screens.single.signInWithProvider
-import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.navigation.NavHostController
-import androidx.navigation.compose.rememberNavController
-import com.android.credentialmanager.CredentialSelectorUiState
+import com.android.credentialmanager.FlowEngine
import com.android.credentialmanager.model.get.CredentialEntryInfo
-import com.android.credentialmanager.R
-import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
import com.android.credentialmanager.ui.components.AccountRow
import com.android.credentialmanager.ui.components.ContinueChip
import com.android.credentialmanager.ui.components.DismissChip
import com.android.credentialmanager.ui.components.SignInHeader
import com.android.credentialmanager.ui.components.SignInOptionsChip
import com.android.credentialmanager.ui.screens.single.SingleAccountScreen
-import com.android.credentialmanager.ui.screens.UiState
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.ScalingLazyColumnState
/**
* Screen that shows sign in with provider credential.
*
- * @param credentialSelectorUiState The app bar view model.
+ * @param entry The password entry.
* @param columnState ScalingLazyColumn configuration to be be applied to SingleAccountScreen
* @param modifier styling for composable
- * @param viewModel ViewModel that updates ui state for this screen
- * @param navController handles navigation events from this screen
+ * @param flowEngine [FlowEngine] that updates ui state for this screen
*/
@OptIn(ExperimentalHorologistApi::class)
@Composable
fun SignInWithProviderScreen(
- credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntry,
- columnState: ScalingLazyColumnState,
- modifier: Modifier = Modifier,
- viewModel: SignInWithProviderViewModel = hiltViewModel(),
- navController: NavHostController = rememberNavController(),
-) {
- viewModel.initialize(credentialSelectorUiState.entry)
-
- val uiState by viewModel.uiState.collectAsStateWithLifecycle()
-
- when (uiState) {
- UiState.CredentialScreen -> {
- SignInWithProviderScreen(
- credentialSelectorUiState.entry,
- columnState,
- modifier,
- viewModel
- )
- }
-
- is UiState.CredentialSelected -> {
- val launcher = rememberLauncherForActivityResult(
- StartBalIntentSenderForResultContract()
- ) {
- viewModel.onInfoRetrieved(it.resultCode, null)
- }
-
- SideEffect {
- (uiState as UiState.CredentialSelected).intentSenderRequest?.let {
- launcher.launch(it)
- }
- }
- }
-
- UiState.Cancel -> {
- // TODO(b/322797032) add valid navigation path here for going back
- navController.popBackStack()
- }
- }
-}
-
-@OptIn(ExperimentalHorologistApi::class)
-@Composable
-fun SignInWithProviderScreen(
entry: CredentialEntryInfo,
columnState: ScalingLazyColumnState,
modifier: Modifier = Modifier,
- viewModel: SignInWithProviderViewModel,
+ flowEngine: FlowEngine,
) {
SingleAccountScreen(
headerContent = {
SignInHeader(
icon = entry.icon,
- title = stringResource(R.string.use_sign_in_with_provider_title,
- entry.providerDisplayName),
+ title = entry.providerDisplayName,
)
},
accountContent = {
@@ -130,12 +73,13 @@
columnState = columnState,
modifier = modifier.padding(horizontal = 10.dp)
) {
- item {
- Column {
- ContinueChip(viewModel::onContinueClick)
- SignInOptionsChip(viewModel::onSignInOptionsClick)
- DismissChip(viewModel::onDismissClick)
- }
- }
+ item {
+ val selectEntry = flowEngine.getEntrySelector()
+ Column {
+ ContinueChip { selectEntry(entry, false) }
+ SignInOptionsChip{ flowEngine.openSecondaryScreen() }
+ DismissChip { flowEngine.cancel() }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderViewModel.kt
deleted file mode 100644
index 7ba45e5..0000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/signInWithProvider/SignInWithProviderViewModel.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.screens.single.signInWithProvider
-
-import android.content.Intent
-import android.credentials.selection.ProviderPendingIntentResponse
-import android.credentials.selection.UserSelectionDialogResult
-import androidx.annotation.MainThread
-import androidx.lifecycle.ViewModel
-import com.android.credentialmanager.ktx.getIntentSenderRequest
-import com.android.credentialmanager.model.Request
-import com.android.credentialmanager.client.CredentialManagerClient
-import com.android.credentialmanager.model.get.CredentialEntryInfo
-import dagger.hilt.android.lifecycle.HiltViewModel
-import com.android.credentialmanager.ui.screens.UiState
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import javax.inject.Inject
-
-/** ViewModel for [SignInWithProviderScreen].*/
-@HiltViewModel
-class SignInWithProviderViewModel @Inject constructor(
- private val credentialManagerClient: CredentialManagerClient,
-) : ViewModel() {
-
- private val _uiState =
- MutableStateFlow<UiState>(UiState.CredentialScreen)
- val uiState: StateFlow<UiState> = _uiState
-
- private lateinit var requestGet: Request.Get
- private lateinit var entryInfo: CredentialEntryInfo
-
- @MainThread
- fun initialize(entry: CredentialEntryInfo) {
- this.entryInfo = entry
- }
-
- fun onDismissClick() {
- _uiState.value = UiState.Cancel
- }
-
- fun onContinueClick() {
- _uiState.value = UiState.CredentialSelected(
- intentSenderRequest = entryInfo.getIntentSenderRequest()
- )
- }
-
- fun onSignInOptionsClick() {
- // TODO(b/322797032) Implement navigation route for single credential screen to multiple
- // credentials
- }
-
- fun onInfoRetrieved(
- resultCode: Int? = null,
- resultData: Intent? = null,
- ) {
- val userSelectionDialogResult = UserSelectionDialogResult(
- requestGet.token,
- entryInfo.providerId,
- entryInfo.entryKey,
- entryInfo.entrySubkey,
- if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
- )
- credentialManagerClient.sendResult(userSelectionDialogResult)
- }
-}
-
diff --git a/packages/EasterEgg/res/values-night/styles.xml b/packages/EasterEgg/res/values-night/styles.xml
index 4edf692..6ea2eae 100644
--- a/packages/EasterEgg/res/values-night/styles.xml
+++ b/packages/EasterEgg/res/values-night/styles.xml
@@ -15,7 +15,7 @@
-->
<resources>
<style name="AppTheme" parent="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen">
- <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
+ <item name="android:windowLayoutInDisplayCutoutMode">always</item>
<item name="android:windowLightNavigationBar">false</item>
</style>
</resources>
diff --git a/packages/EasterEgg/res/values/styles.xml b/packages/EasterEgg/res/values/styles.xml
index e576526..4a2cb48f6 100644
--- a/packages/EasterEgg/res/values/styles.xml
+++ b/packages/EasterEgg/res/values/styles.xml
@@ -16,7 +16,7 @@
<resources>
<style name="AppTheme" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar.Fullscreen">
- <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
+ <item name="android:windowLayoutInDisplayCutoutMode">always</item>
<item name="android:windowLightNavigationBar">true</item>
</style>
diff --git a/packages/PackageInstaller/res/values-el/strings.xml b/packages/PackageInstaller/res/values-el/strings.xml
index 3c37df6..6f7a433 100644
--- a/packages/PackageInstaller/res/values-el/strings.xml
+++ b/packages/PackageInstaller/res/values-el/strings.xml
@@ -48,7 +48,7 @@
<string name="update_anyway" msgid="8792432341346261969">"Να ενημερωθεί ούτως ή άλλως"</string>
<string name="manage_applications" msgid="5400164782453975580">"Διαχ. εφαρμογών"</string>
<string name="out_of_space_dlg_title" msgid="4156690013884649502">"Δεν υπάρχει χώρος"</string>
- <string name="out_of_space_dlg_text" msgid="8727714096031856231">"Δεν ήταν δυνατή η εγκατάσταση της εφαρμογής <xliff:g id="APP_NAME">%1$s</xliff:g>. Απελευθερώστε λίγο χώρο και προσπαθήστε ξανά."</string>
+ <string name="out_of_space_dlg_text" msgid="8727714096031856231">"Δεν ήταν δυνατή η εγκατάσταση της εφαρμογής <xliff:g id="APP_NAME">%1$s</xliff:g>. Αποδεσμεύστε λίγο χώρο και προσπαθήστε ξανά."</string>
<string name="app_not_found_dlg_title" msgid="5107924008597470285">"Η εφαρμογή δεν βρέθηκε"</string>
<string name="app_not_found_dlg_text" msgid="5219983779377811611">"Η εφαρμογή δεν βρέθηκε στη λίστα εγκατεστημένων εφαρμογών."</string>
<string name="user_is_not_allowed_dlg_title" msgid="6915293433252210232">"Δεν επιτρέπεται"</string>
diff --git a/packages/PackageInstaller/res/values-fr-feminine/strings.xml b/packages/PackageInstaller/res/values-fr-feminine/strings.xml
new file mode 100644
index 0000000..c6b8de7
--- /dev/null
+++ b/packages/PackageInstaller/res/values-fr-feminine/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Votre téléphone et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être la seule responsable de tout dommage causé à votre téléphone ou de toute perte de données pouvant découler de son utilisation."</string>
+ <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Votre tablette et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être la seule responsable de tout dommage causé à votre tablette ou de toute perte de données pouvant découler de son utilisation."</string>
+ <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Votre téléviseur et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être la seule responsable de tout dommage causé à votre téléviseur ou de toute perte de données pouvant découler de son utilisation."</string>
+ <string name="unarchive_error_offline_title" msgid="4021785324565678605">"Vous n\'êtes pas connectée"</string>
+</resources>
diff --git a/packages/PackageInstaller/res/values-fr-masculine/strings.xml b/packages/PackageInstaller/res/values-fr-masculine/strings.xml
new file mode 100644
index 0000000..b4a40a0
--- /dev/null
+++ b/packages/PackageInstaller/res/values-fr-masculine/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Votre téléphone et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être le seul responsable de tout dommage causé à votre téléphone ou de toute perte de données pouvant découler de son utilisation."</string>
+ <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Votre tablette et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être le seul responsable de tout dommage causé à votre tablette ou de toute perte de données pouvant découler de son utilisation."</string>
+ <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Votre téléviseur et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être le seul responsable de tout dommage causé à votre téléviseur ou de toute perte de données pouvant découler de son utilisation."</string>
+ <string name="unarchive_error_offline_title" msgid="4021785324565678605">"Vous n\'êtes pas connecté"</string>
+</resources>
diff --git a/packages/PackageInstaller/res/values-fr-neuter/strings.xml b/packages/PackageInstaller/res/values-fr-neuter/strings.xml
new file mode 100644
index 0000000..9362b33
--- /dev/null
+++ b/packages/PackageInstaller/res/values-fr-neuter/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Votre téléphone et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être l\'unique responsable de tout dommage causé à votre téléphone ou de toute perte de données pouvant découler de son utilisation."</string>
+ <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Votre tablette et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être l\'unique responsable de tout dommage causé à votre tablette ou de toute perte de données pouvant découler de son utilisation."</string>
+ <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Votre téléviseur et vos données à caractère personnel sont plus vulnérables aux attaques d\'applications inconnues. En installant cette application, vous acceptez d\'être l\'unique responsable de tout dommage causé à votre téléviseur ou de toute perte de données pouvant découler de son utilisation."</string>
+ <string name="unarchive_error_offline_title" msgid="4021785324565678605">"Vous n\'êtes pas connecté·e"</string>
+</resources>
diff --git a/packages/PackageInstaller/res/values-watch/themes.xml b/packages/PackageInstaller/res/values-watch/themes.xml
index 5e52008..814d08a 100644
--- a/packages/PackageInstaller/res/values-watch/themes.xml
+++ b/packages/PackageInstaller/res/values-watch/themes.xml
@@ -16,5 +16,11 @@
-->
<resources>
- <style name="DialogWhenLarge" parent="@android:style/Theme.DeviceDefault.NoActionBar"/>
+ <style name="Theme.AlertDialogActivity"
+ parent="@android:style/Theme.DeviceDefault.Dialog.Alert">
+ <item name="alertDialogStyle">@style/AlertDialog</item>
+ <item name="android:windowActionBar">false</item>
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowAnimationStyle">@null</item>
+ </style>
</resources>
diff --git a/packages/SettingsLib/DataStore/Android.bp b/packages/SettingsLib/DataStore/Android.bp
index 868a4a5..9fafcab 100644
--- a/packages/SettingsLib/DataStore/Android.bp
+++ b/packages/SettingsLib/DataStore/Android.bp
@@ -11,6 +11,8 @@
static_libs: [
"androidx.annotation_annotation",
"androidx.collection_collection-ktx",
+ "androidx.core_core-ktx",
"guava",
],
+ kotlincflags: ["-Xjvm-default=all"],
}
diff --git a/packages/SettingsLib/DataStore/README.md b/packages/SettingsLib/DataStore/README.md
new file mode 100644
index 0000000..30cb993
--- /dev/null
+++ b/packages/SettingsLib/DataStore/README.md
@@ -0,0 +1,164 @@
+# Datastore library
+
+This library aims to manage datastore in a consistent way.
+
+## Overview
+
+A datastore is required to extend the `BackupRestoreStorage` class and implement
+either `Observable` or `KeyedObservable` interface, which enforces:
+
+- Backup and restore: Datastore should support
+ [data backup](https://developer.android.com/guide/topics/data/backup) to
+ preserve user experiences on a new device.
+- Observer pattern: The
+ [observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) allows to
+ monitor data change in the datastore and
+ - trigger
+ [BackupManager.dataChanged](https://developer.android.com/reference/android/app/backup/BackupManager#dataChanged\(\))
+ automatically.
+ - track data change event to log metrics.
+ - update internal state and take action.
+
+### Backup and restore
+
+The Android backup framework provides
+[BackupAgentHelper](https://developer.android.com/reference/android/app/backup/BackupAgentHelper)
+and
+[BackupHelper](https://developer.android.com/reference/android/app/backup/BackupHelper)
+to back up a datastore. However, there are several caveats when implement
+`BackupHelper`:
+
+- performBackup: The data is updated incrementally but it is not well
+ documented. The `ParcelFileDescriptor` state parameters are normally ignored
+ and data is updated even there is no change.
+- restoreEntity: The implementation must take care not to seek or close the
+ underlying data source, nor read more than size() bytes from the stream when
+ restore (see
+ [BackupDataInputStream](https://developer.android.com/reference/android/app/backup/BackupDataInputStream)).
+ It is possible a `BackupHelper` prevents other `BackupHelper`s from
+ restoring data.
+- writeNewStateDescription: Existing implementations rarely notice that this
+ callback is invoked after all entities are restored, and check if necessary
+ data are all restored in `restoreEntity` (e.g.
+ [BatteryBackupHelper](https://cs.android.com/android/platform/superproject/main/+/main:packages/apps/Settings/src/com/android/settings/fuelgauge/BatteryBackupHelper.java;l=144;drc=cca804e1ed504e2d477be1e3db00fb881ca32736)),
+ which is not robust sometimes.
+
+This library provides more clear API and offers some improvements:
+
+- The implementation only needs to focus on the `BackupRestoreEntity`
+ interface. The `InputStream` of restore will ensure bounded data are read,
+ and close the stream will be no-op.
+- The library computes checksum of the backup data automatically, so that
+ unchanged data will not be sent to Android backup system.
+- Data compression is supported:
+ - ZIP best compression is enabled by default, no extra effort needs to be
+ taken.
+ - It is safe to switch between compression and no compression in future,
+ the backup data will add 1 byte header to recognize the codec.
+ - To support other compression algorithms, simply wrap over the
+ `InputStream` and `OutputStream`. Actually, the checksum is computed in
+ this way by
+ [CheckedInputStream](https://developer.android.com/reference/java/util/zip/CheckedInputStream)
+ and
+ [CheckedOutputStream](https://developer.android.com/reference/java/util/zip/CheckedOutputStream),
+ see `BackupRestoreStorage` implementation for more details.
+- Enhanced forward compatibility for file is enabled: If a backup includes
+ data that didn't exist in earlier versions of the app, the data can still be
+ successfully restored in those older versions. This is achieved by extending
+ the `BackupRestoreFileStorage` class, and `BackupRestoreFileArchiver` will
+ treat each file as an entity and do the backup / restore.
+- Manual `BackupManager.dataChanged` call is unnecessary now, the library will
+ do the invocation (see next section).
+
+### Observer pattern
+
+Manual `BackupManager.dataChanged` call is required by current backup framework.
+In practice, it is found that `SharedPreferences` usages foget to invoke the
+API. Besides, there are common use cases to log metrics when data is changed.
+Consequently, observer pattern is employed to resolve the issues.
+
+If the datastore is key-value based (e.g. `SharedPreferences`), implements the
+`KeyedObservable` interface to offer fine-grained observer. Otherwise,
+implements `Observable`. The library provides thread-safe implementations
+(`KeyedDataObservable` / `DataObservable`), and Kotlin delegation will be
+helpful.
+
+Keep in mind that the implementation should call `KeyedObservable.notifyChange`
+/ `Observable.notifyChange` whenever internal data is changed, so that the
+registered observer will be notified properly.
+
+## Usage and example
+
+For `SharedPreferences` use case, leverage the `SharedPreferencesStorage`. To
+back up other file based storage, extend the `BackupRestoreFileStorage` class.
+
+Here is an example of customized datastore, which has a string to back up:
+
+```kotlin
+class MyDataStore : ObservableBackupRestoreStorage() {
+ // Another option is make it a StringEntity type and maintain a String field inside StringEntity
+ @Volatile // backup/restore happens on Binder thread
+ var data: String? = null
+ private set
+
+ fun setData(data: String?) {
+ this.data = data
+ notifyChange(ChangeReason.UPDATE)
+ }
+
+ override val name: String
+ get() = "MyData"
+
+ override fun createBackupRestoreEntities(): List<BackupRestoreEntity> =
+ listOf(StringEntity("data"))
+
+ private inner class StringEntity(override val key: String) : BackupRestoreEntity {
+ override fun backup(
+ backupContext: BackupContext,
+ outputStream: OutputStream,
+ ) =
+ if (data != null) {
+ outputStream.write(data!!.toByteArray(UTF_8))
+ EntityBackupResult.UPDATE
+ } else {
+ EntityBackupResult.DELETE
+ }
+
+ override fun restore(restoreContext: RestoreContext, inputStream: InputStream) {
+ data = String(inputStream.readAllBytes(), UTF_8)
+ // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you
+ }
+ }
+
+ override fun onRestoreFinished() {
+ // TODO: Update state with the restored data. Use this callback instead "restore()" in case
+ // the restore action involves several entities.
+ // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you
+ }
+}
+```
+
+In the application class:
+
+```kotlin
+class MyApplication : Application() {
+ override fun onCreate() {
+ super.onCreate();
+ BackupRestoreStorageManager.getInstance(this).add(MyDataStore());
+ }
+}
+```
+
+In the custom `BackupAgentHelper` class:
+
+```kotlin
+class MyBackupAgentHelper : BackupAgentHelper() {
+ override fun onCreate() {
+ BackupRestoreStorageManager.getInstance(this).addBackupAgentHelpers(this);
+ }
+
+ override fun onRestoreFinished() {
+ BackupRestoreStorageManager.getInstance(this).onRestoreFinished();
+ }
+}
+```
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupCodec.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupCodec.kt
new file mode 100644
index 0000000..550645f
--- /dev/null
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupCodec.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import androidx.annotation.IntDef
+import java.io.InputStream
+import java.io.OutputStream
+import java.util.zip.Deflater
+import java.util.zip.DeflaterOutputStream
+import java.util.zip.InflaterInputStream
+
+/** Unique id of the codec. */
+@Target(AnnotationTarget.TYPE)
+@IntDef(
+ BackupCodecId.NO_OP.toInt(),
+ BackupCodecId.ZIP.toInt(),
+)
+@Retention(AnnotationRetention.SOURCE)
+annotation class BackupCodecId {
+ companion object {
+ /** Unknown reason of the change. */
+ const val NO_OP: Byte = 0
+ /** Data is updated. */
+ const val ZIP: Byte = 1
+ }
+}
+
+/** How to encode/decode the backup data. */
+interface BackupCodec {
+ /** Unique id of the codec. */
+ val id: @BackupCodecId Byte
+
+ /** Name of the codec. */
+ val name: String
+
+ /** Encodes the backup data. */
+ fun encode(outputStream: OutputStream): OutputStream
+
+ /** Decodes the backup data. */
+ fun decode(inputStream: InputStream): InputStream
+
+ companion object {
+ @JvmStatic
+ fun fromId(id: @BackupCodecId Byte): BackupCodec =
+ when (id) {
+ BackupCodecId.NO_OP -> BackupNoOpCodec()
+ BackupCodecId.ZIP -> BackupZipCodec.BEST_COMPRESSION
+ else -> throw IllegalArgumentException("Unknown codec id $id")
+ }
+ }
+}
+
+/** Codec without any additional encoding/decoding. */
+class BackupNoOpCodec : BackupCodec {
+ override val id
+ get() = BackupCodecId.NO_OP
+
+ override val name
+ get() = "N/A"
+
+ override fun encode(outputStream: OutputStream) = outputStream
+
+ override fun decode(inputStream: InputStream) = inputStream
+}
+
+/** Codec with ZIP compression. */
+class BackupZipCodec(
+ private val compressionLevel: Int,
+ override val name: String,
+) : BackupCodec {
+ override val id
+ get() = BackupCodecId.ZIP
+
+ override fun encode(outputStream: OutputStream) =
+ DeflaterOutputStream(outputStream, Deflater(compressionLevel))
+
+ override fun decode(inputStream: InputStream) = InflaterInputStream(inputStream)
+
+ companion object {
+ val DEFAULT_COMPRESSION = BackupZipCodec(Deflater.DEFAULT_COMPRESSION, "ZipDefault")
+ val BEST_COMPRESSION = BackupZipCodec(Deflater.BEST_COMPRESSION, "ZipBestCompression")
+ val BEST_SPEED = BackupZipCodec(Deflater.BEST_SPEED, "ZipBestSpeed")
+ }
+}
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreContext.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreContext.kt
index c6d6f77..8fe618d 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreContext.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreContext.kt
@@ -20,7 +20,6 @@
import android.app.backup.BackupDataOutput
import android.app.backup.BackupHelper
import android.os.Build
-import android.os.ParcelFileDescriptor
import androidx.annotation.RequiresApi
/**
@@ -31,23 +30,8 @@
*/
class BackupContext
internal constructor(
- /**
- * An open, read-only file descriptor pointing to the last backup state provided by the
- * application. May be null, in which case no prior state is being provided and the application
- * should perform a full backup.
- *
- * TODO: the state should support marshall/unmarshall for incremental back up.
- */
- val oldState: ParcelFileDescriptor?,
-
/** An open, read/write BackupDataOutput pointing to the backup data destination. */
private val data: BackupDataOutput,
-
- /**
- * An open, read/write file descriptor pointing to an empty file. The application should record
- * the final backup.
- */
- val newState: ParcelFileDescriptor,
) {
/**
* The quota in bytes for the application's current backup operation.
@@ -68,5 +52,9 @@
@RequiresApi(Build.VERSION_CODES.P) get() = data.transportFlags
}
-/** Context for restore. */
-class RestoreContext(val key: String)
+/**
+ * Context for restore.
+ *
+ * @param key Entity key
+ */
+class RestoreContext internal constructor(val key: String)
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt
index 6a7ef5a..817ee4c 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt
@@ -36,6 +36,13 @@
val key: String
/**
+ * Codec used to encode/decode the backup data.
+ *
+ * When it is null, the [BackupRestoreStorage.defaultCodec] will be used.
+ */
+ fun codec(): BackupCodec? = null
+
+ /**
* Backs up the entity.
*
* @param backupContext context for backup
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileArchiver.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileArchiver.kt
new file mode 100644
index 0000000..9d3fb66
--- /dev/null
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileArchiver.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import android.app.backup.BackupDataInputStream
+import android.content.Context
+import android.util.Log
+import java.io.File
+import java.io.InputStream
+import java.io.OutputStream
+import java.util.zip.CheckedInputStream
+
+/**
+ * File archiver to handle backup and restore for all the [BackupRestoreFileStorage] subclasses.
+ *
+ * Compared with [android.app.backup.FileBackupHelper], this class supports forward-compatibility
+ * like the [com.google.android.libraries.backup.PersistentBackupAgentHelper]: the app does not need
+ * to know the list of files in advance at restore time.
+ */
+internal class BackupRestoreFileArchiver(
+ private val context: Context,
+ private val fileStorages: List<BackupRestoreFileStorage>,
+) : BackupRestoreStorage() {
+ override val name: String
+ get() = "file_archiver"
+
+ override fun createBackupRestoreEntities(): List<BackupRestoreEntity> =
+ fileStorages.map { it.toBackupRestoreEntity() }
+
+ override fun wrapBackupOutputStream(codec: BackupCodec, outputStream: OutputStream) =
+ outputStream
+
+ override fun wrapRestoreInputStream(codec: BackupCodec, inputStream: InputStream) = inputStream
+
+ override fun restoreEntity(data: BackupDataInputStream) {
+ val key = data.key
+ val fileStorage = fileStorages.firstOrNull { it.storageFilePath == key }
+ val file =
+ if (fileStorage != null) {
+ if (!fileStorage.enableRestore()) {
+ Log.i(LOG_TAG, "[$name] $key restore disabled")
+ return
+ }
+ fileStorage.restoreFile
+ } else { // forward-compatibility
+ Log.i(LOG_TAG, "Restore unknown file $key")
+ File(context.dataDirCompat, key)
+ }
+ Log.i(LOG_TAG, "[$name] Restore ${data.size()} bytes for $key to $file")
+ val inputStream = LimitedNoCloseInputStream(data)
+ val checksum = createChecksum()
+ val checkedInputStream = CheckedInputStream(inputStream, checksum)
+ try {
+ val codec = BackupCodec.fromId(checkedInputStream.read().toByte())
+ if (fileStorage != null && fileStorage.defaultCodec().id != codec.id) {
+ Log.i(
+ LOG_TAG,
+ "[$name] $key different codec: ${codec.id}, ${fileStorage.defaultCodec().id}"
+ )
+ }
+ file.parentFile?.mkdirs() // ensure parent folders are created
+ val wrappedInputStream = codec.decode(checkedInputStream)
+ val bytesCopied = file.outputStream().use { wrappedInputStream.copyTo(it) }
+ Log.i(LOG_TAG, "[$name] $key restore $bytesCopied bytes with ${codec.name}")
+ fileStorage?.onRestoreFinished(file)
+ entityStates[key] = checksum.value
+ } catch (e: Exception) {
+ Log.e(LOG_TAG, "[$name] Fail to restore $key", e)
+ }
+ }
+
+ override fun onRestoreFinished() {
+ fileStorages.forEach { it.onRestoreFinished() }
+ }
+}
+
+private fun BackupRestoreFileStorage.toBackupRestoreEntity() =
+ object : BackupRestoreEntity {
+ override val key: String
+ get() = storageFilePath
+
+ override fun backup(
+ backupContext: BackupContext,
+ outputStream: OutputStream,
+ ): EntityBackupResult {
+ if (!enableBackup(backupContext)) {
+ Log.i(LOG_TAG, "[$name] $key backup disabled")
+ return EntityBackupResult.INTACT
+ }
+ val file = backupFile
+ prepareBackup(file)
+ if (!file.exists()) {
+ Log.i(LOG_TAG, "[$name] $key not exist")
+ return EntityBackupResult.DELETE
+ }
+ val codec = codec() ?: defaultCodec()
+ // MUST close to flush the data
+ wrapBackupOutputStream(codec, outputStream).use { stream ->
+ val bytesCopied = file.inputStream().use { it.copyTo(stream) }
+ Log.i(LOG_TAG, "[$name] $key backup $bytesCopied bytes with ${codec.name}")
+ }
+ onBackupFinished(file)
+ return EntityBackupResult.UPDATE
+ }
+
+ override fun restore(restoreContext: RestoreContext, inputStream: InputStream) {
+ // no-op, BackupRestoreFileArchiver#restoreEntity will restore files
+ }
+ }
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileStorage.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileStorage.kt
new file mode 100644
index 0000000..b531bd1
--- /dev/null
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreFileStorage.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import android.content.Context
+import androidx.core.content.ContextCompat
+import java.io.File
+
+/**
+ * A file-based storage with backup and restore support.
+ *
+ * [BackupRestoreFileArchiver] will handle the backup and restore based on file path for all
+ * subclasses.
+ *
+ * @param context Context to retrieve data dir
+ * @param storageFilePath Storage file path, which MUST be relative to the [Context.getDataDir]
+ * folder. This is used as the entity name for backup and restore.
+ */
+abstract class BackupRestoreFileStorage(
+ val context: Context,
+ val storageFilePath: String,
+) : BackupRestoreStorage() {
+
+ /** The absolute path of the file to backup. */
+ open val backupFile: File
+ get() = File(context.dataDirCompat, storageFilePath)
+
+ /** The absolute path of the file to restore. */
+ open val restoreFile: File
+ get() = backupFile
+
+ fun checkFilePaths() {
+ if (storageFilePath.isEmpty() || storageFilePath[0] == File.separatorChar) {
+ throw IllegalArgumentException("$storageFilePath is not valid path")
+ }
+ if (!backupFile.isAbsolute) {
+ throw IllegalArgumentException("backupFile is not absolute")
+ }
+ if (!restoreFile.isAbsolute) {
+ throw IllegalArgumentException("restoreFile is not absolute")
+ }
+ }
+
+ /**
+ * Callback before [backupFile] is backed up.
+ *
+ * @param file equals to [backupFile]
+ */
+ open fun prepareBackup(file: File) {}
+
+ /**
+ * Callback when [backupFile] is restored.
+ *
+ * @param file equals to [backupFile]
+ */
+ open fun onBackupFinished(file: File) {}
+
+ /**
+ * Callback when [restoreFile] is restored.
+ *
+ * @param file equals to [restoreFile]
+ */
+ open fun onRestoreFinished(file: File) {}
+
+ final override fun createBackupRestoreEntities(): List<BackupRestoreEntity> = listOf()
+}
+
+internal val Context.dataDirCompat: File
+ get() = ContextCompat.getDataDir(this)!!
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt
index 88d9dd6..c4c00cb 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt
@@ -22,11 +22,21 @@
import android.app.backup.BackupHelper
import android.os.ParcelFileDescriptor
import android.util.Log
+import androidx.collection.MutableScatterMap
import com.google.common.io.ByteStreams
import java.io.ByteArrayOutputStream
+import java.io.DataInputStream
+import java.io.DataOutputStream
+import java.io.EOFException
+import java.io.FileInputStream
+import java.io.FileOutputStream
import java.io.FilterInputStream
import java.io.InputStream
import java.io.OutputStream
+import java.util.zip.CRC32
+import java.util.zip.CheckedInputStream
+import java.util.zip.CheckedOutputStream
+import java.util.zip.Checksum
internal const val LOG_TAG = "BackupRestoreStorage"
@@ -45,87 +55,197 @@
*/
abstract val name: String
- private val entities: List<BackupRestoreEntity> by lazy { createBackupRestoreEntities() }
+ /**
+ * Entity states represented by checksum.
+ *
+ * Map key is the entity key, map value is the checksum of backup data.
+ */
+ protected val entityStates = MutableScatterMap<String, Long>()
+
+ /** Entities created by [createBackupRestoreEntities]. This field is for restore only. */
+ private var entities: List<BackupRestoreEntity>? = null
/** Entities to back up and restore. */
abstract fun createBackupRestoreEntities(): List<BackupRestoreEntity>
- override fun performBackup(
+ /** Default codec used to encode/decode the entity data. */
+ open fun defaultCodec(): BackupCodec = BackupZipCodec.BEST_COMPRESSION
+
+ final override fun performBackup(
oldState: ParcelFileDescriptor?,
data: BackupDataOutput,
newState: ParcelFileDescriptor,
) {
- val backupContext = BackupContext(oldState, data, newState)
+ oldState.readEntityStates(entityStates)
+ val backupContext = BackupContext(data)
if (!enableBackup(backupContext)) {
Log.i(LOG_TAG, "[$name] Backup disabled")
return
}
Log.i(LOG_TAG, "[$name] Backup start")
+ val checksum = createChecksum()
+ // recreate entities for backup to avoid stale states
+ val entities = createBackupRestoreEntities()
for (entity in entities) {
val key = entity.key
val outputStream = ByteArrayOutputStream()
+ checksum.reset()
+ val checkedOutputStream = CheckedOutputStream(outputStream, checksum)
+ val codec = entity.codec() ?: defaultCodec()
val result =
try {
- entity.backup(backupContext, wrapBackupOutputStream(outputStream))
+ entity.backup(backupContext, wrapBackupOutputStream(codec, checkedOutputStream))
} catch (exception: Exception) {
Log.e(LOG_TAG, "[$name] Fail to backup entity $key", exception)
continue
}
when (result) {
EntityBackupResult.UPDATE -> {
- val payload = outputStream.toByteArray()
- val size = payload.size
- data.writeEntityHeader(key, size)
- data.writeEntityData(payload, size)
- Log.i(LOG_TAG, "[$name] Backup entity $key: $size bytes")
+ val value = checksum.value
+ if (entityStates.put(key, value) != value) {
+ val payload = outputStream.toByteArray()
+ val size = payload.size
+ data.writeEntityHeader(key, size)
+ data.writeEntityData(payload, size)
+ Log.i(LOG_TAG, "[$name] Backup entity $key: $size bytes")
+ } else {
+ Log.i(
+ LOG_TAG,
+ "[$name] Backup entity $key unchanged: ${outputStream.size()} bytes"
+ )
+ }
}
EntityBackupResult.INTACT -> {
Log.i(LOG_TAG, "[$name] Backup entity $key intact")
}
EntityBackupResult.DELETE -> {
+ entityStates.remove(key)
data.writeEntityHeader(key, -1)
Log.i(LOG_TAG, "[$name] Backup entity $key deleted")
}
}
}
+ newState.writeAndClearEntityStates()
Log.i(LOG_TAG, "[$name] Backup end")
}
/** Returns if backup is enabled. */
open fun enableBackup(backupContext: BackupContext): Boolean = true
- fun wrapBackupOutputStream(outputStream: OutputStream): OutputStream {
- return outputStream
+ open fun wrapBackupOutputStream(codec: BackupCodec, outputStream: OutputStream): OutputStream {
+ // write a codec id header for safe restore
+ outputStream.write(codec.id.toInt())
+ return codec.encode(outputStream)
}
+ /** This callback is invoked for every backed up entity. */
override fun restoreEntity(data: BackupDataInputStream) {
val key = data.key
if (!enableRestore()) {
Log.i(LOG_TAG, "[$name] Restore disabled, ignore entity $key")
return
}
- val entity = entities.firstOrNull { it.key == key }
+ val entity = ensureEntities().firstOrNull { it.key == key }
if (entity == null) {
Log.w(LOG_TAG, "[$name] Cannot find handler for entity $key")
return
}
Log.i(LOG_TAG, "[$name] Restore $key: ${data.size()} bytes")
val restoreContext = RestoreContext(key)
+ val codec = entity.codec() ?: defaultCodec()
+ val inputStream = LimitedNoCloseInputStream(data)
+ val checksum = createChecksum()
+ val checkedInputStream = CheckedInputStream(inputStream, checksum)
try {
- entity.restore(restoreContext, wrapRestoreInputStream(data))
+ entity.restore(restoreContext, wrapRestoreInputStream(codec, checkedInputStream))
+ entityStates[key] = checksum.value
} catch (exception: Exception) {
Log.e(LOG_TAG, "[$name] Fail to restore entity $key", exception)
}
}
+ private fun ensureEntities(): List<BackupRestoreEntity> =
+ entities ?: createBackupRestoreEntities().also { entities = it }
+
/** Returns if restore is enabled. */
open fun enableRestore(): Boolean = true
- fun wrapRestoreInputStream(inputStream: BackupDataInputStream): InputStream {
- return LimitedNoCloseInputStream(inputStream)
+ open fun wrapRestoreInputStream(
+ codec: BackupCodec,
+ inputStream: InputStream,
+ ): InputStream {
+ // read the codec id first to check if it is expected codec
+ val id = inputStream.read()
+ val expectedId = codec.id.toInt()
+ if (id == expectedId) return codec.decode(inputStream)
+ Log.i(LOG_TAG, "Expect codec id $expectedId but got $id")
+ return BackupCodec.fromId(id.toByte()).decode(inputStream)
}
- override fun writeNewStateDescription(newState: ParcelFileDescriptor) {}
+ final override fun writeNewStateDescription(newState: ParcelFileDescriptor) {
+ entities = null // clear to reduce memory footprint
+ newState.writeAndClearEntityStates()
+ onRestoreFinished()
+ }
+
+ /** Callbacks when restore finished. */
+ open fun onRestoreFinished() {}
+
+ private fun ParcelFileDescriptor?.readEntityStates(state: MutableScatterMap<String, Long>) {
+ state.clear()
+ if (this == null) return
+ // do not close the streams
+ val fileInputStream = FileInputStream(fileDescriptor)
+ val dataInputStream = DataInputStream(fileInputStream)
+ try {
+ val version = dataInputStream.readByte()
+ if (version != STATE_VERSION) {
+ Log.w(
+ LOG_TAG,
+ "[$name] Unexpected state version, read:$version, expected:$STATE_VERSION"
+ )
+ return
+ }
+ var count = dataInputStream.readInt()
+ while (count-- > 0) {
+ val key = dataInputStream.readUTF()
+ val checksum = dataInputStream.readLong()
+ state[key] = checksum
+ }
+ } catch (exception: Exception) {
+ if (exception is EOFException) {
+ Log.d(LOG_TAG, "[$name] Hit EOF when read state file")
+ } else {
+ Log.e(LOG_TAG, "[$name] Fail to read state file", exception)
+ }
+ state.clear()
+ }
+ }
+
+ private fun ParcelFileDescriptor.writeAndClearEntityStates() {
+ // do not close the streams
+ val fileOutputStream = FileOutputStream(fileDescriptor)
+ val dataOutputStream = DataOutputStream(fileOutputStream)
+ try {
+ dataOutputStream.writeByte(STATE_VERSION.toInt())
+ dataOutputStream.writeInt(entityStates.size)
+ entityStates.forEach { key, value ->
+ dataOutputStream.writeUTF(key)
+ dataOutputStream.writeLong(value)
+ }
+ } catch (exception: Exception) {
+ Log.e(LOG_TAG, "[$name] Fail to write state file", exception)
+ }
+ entityStates.clear()
+ entityStates.trim() // trim to reduce memory footprint
+ }
+
+ companion object {
+ private const val STATE_VERSION: Byte = 0
+
+ /** Checksum for entity backup data. */
+ fun createChecksum(): Checksum = CRC32()
+ }
}
/**
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt
index 221e2e8..cfdcaff 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt
@@ -26,32 +26,30 @@
/** Manager of [BackupRestoreStorage]. */
class BackupRestoreStorageManager private constructor(private val application: Application) {
- private val storages = ConcurrentHashMap<String, BackupRestoreStorage>()
+ private val storageWrappers = ConcurrentHashMap<String, StorageWrapper>()
private val executor = MoreExecutors.directExecutor()
- private val observer = Observer { reason -> notifyBackupManager(null, reason) }
-
- private val keyedObserver =
- KeyedObserver<Any?> { key, reason -> notifyBackupManager(key, reason) }
-
- private fun notifyBackupManager(key: Any?, reason: Int) {
- // prefer not triggering backup immediately after restore
- if (reason == ChangeReason.RESTORE) return
- // TODO: log storage name
- Log.d(LOG_TAG, "Notify BackupManager data changed for change: key=$key")
- BackupManager.dataChanged(application.packageName)
- }
-
/**
* Adds all the registered [BackupRestoreStorage] as the helpers of given [BackupAgentHelper].
*
+ * All [BackupRestoreFileStorage]s will be wrapped as a single [BackupRestoreFileArchiver].
+ *
* @see BackupAgentHelper.addHelper
*/
fun addBackupAgentHelpers(backupAgentHelper: BackupAgentHelper) {
- for ((keyPrefix, storage) in storages) {
- backupAgentHelper.addHelper(keyPrefix, storage)
+ val fileStorages = mutableListOf<BackupRestoreFileStorage>()
+ for ((keyPrefix, storageWrapper) in storageWrappers) {
+ val storage = storageWrapper.storage
+ if (storage is BackupRestoreFileStorage) {
+ fileStorages.add(storage)
+ } else {
+ backupAgentHelper.addHelper(keyPrefix, storage)
+ }
}
+ // Always add file archiver even fileStorages is empty to handle forward compatibility
+ val fileArchiver = BackupRestoreFileArchiver(application, fileStorages)
+ backupAgentHelper.addHelper(fileArchiver.name, fileArchiver)
}
/**
@@ -60,15 +58,8 @@
* The observers of the storages will be notified.
*/
fun onRestoreFinished() {
- for (storage in storages.values) {
- storage.notifyRestoreFinished()
- }
- }
-
- private fun BackupRestoreStorage.notifyRestoreFinished() {
- when (this) {
- is KeyedObservable<*> -> notifyChange(ChangeReason.RESTORE)
- is Observable -> notifyChange(ChangeReason.RESTORE)
+ for (storageWrapper in storageWrappers.values) {
+ storageWrapper.notifyRestoreFinished()
}
}
@@ -87,51 +78,84 @@
* The storage MUST implement [KeyedObservable] or [Observable].
*/
fun add(storage: BackupRestoreStorage) {
+ if (storage is BackupRestoreFileStorage) storage.checkFilePaths()
val name = storage.name
- val oldStorage = storages.put(name, storage)
+ val oldStorage = storageWrappers.put(name, StorageWrapper(storage))?.storage
if (oldStorage != null) {
throw IllegalStateException(
"Storage name '$name' conflicts:\n\told: $oldStorage\n\tnew: $storage"
)
}
- storage.addObserver()
- }
-
- private fun BackupRestoreStorage.addObserver() {
- when (this) {
- is KeyedObservable<*> -> addObserver(keyedObserver, executor)
- is Observable -> addObserver(observer, executor)
- else ->
- throw IllegalArgumentException(
- "$this does not implement either KeyedObservable or Observable"
- )
- }
}
/** Removes all the storages. */
fun removeAll() {
- for ((name, _) in storages) remove(name)
+ for ((name, _) in storageWrappers) remove(name)
}
/** Removes storage with given name. */
fun remove(name: String): BackupRestoreStorage? {
- val storage = storages.remove(name)
- storage?.removeObserver()
- return storage
- }
-
- private fun BackupRestoreStorage.removeObserver() {
- when (this) {
- is KeyedObservable<*> -> removeObserver(keyedObserver)
- is Observable -> removeObserver(observer)
- }
+ val storageWrapper = storageWrappers.remove(name)
+ storageWrapper?.removeObserver()
+ return storageWrapper?.storage
}
/** Returns storage with given name. */
- fun get(name: String): BackupRestoreStorage? = storages[name]
+ fun get(name: String): BackupRestoreStorage? = storageWrappers[name]?.storage
/** Returns storage with given name, exception is raised if not found. */
- fun getOrThrow(name: String): BackupRestoreStorage = storages[name]!!
+ fun getOrThrow(name: String): BackupRestoreStorage = storageWrappers[name]!!.storage
+
+ private inner class StorageWrapper(val storage: BackupRestoreStorage) :
+ Observer, KeyedObserver<Any?> {
+ init {
+ when (storage) {
+ is KeyedObservable<*> -> storage.addObserver(this, executor)
+ is Observable -> storage.addObserver(this, executor)
+ else ->
+ throw IllegalArgumentException(
+ "$this does not implement either KeyedObservable or Observable"
+ )
+ }
+ }
+
+ override fun onChanged(reason: Int) = onKeyChanged(null, reason)
+
+ override fun onKeyChanged(key: Any?, reason: Int) {
+ notifyBackupManager(key, reason)
+ }
+
+ private fun notifyBackupManager(key: Any?, reason: Int) {
+ val name = storage.name
+ // prefer not triggering backup immediately after restore
+ if (reason == ChangeReason.RESTORE) {
+ Log.d(
+ LOG_TAG,
+ "Notify BackupManager dataChanged ignored for restore: storage=$name key=$key"
+ )
+ return
+ }
+ Log.d(
+ LOG_TAG,
+ "Notify BackupManager dataChanged: storage=$name key=$key reason=$reason"
+ )
+ BackupManager.dataChanged(application.packageName)
+ }
+
+ fun removeObserver() {
+ when (storage) {
+ is KeyedObservable<*> -> storage.removeObserver(this)
+ is Observable -> storage.removeObserver(this)
+ }
+ }
+
+ fun notifyRestoreFinished() {
+ when (storage) {
+ is KeyedObservable<*> -> storage.notifyChange(ChangeReason.RESTORE)
+ is Observable -> storage.notifyChange(ChangeReason.RESTORE)
+ }
+ }
+ }
companion object {
@Volatile private var instance: BackupRestoreStorageManager? = null
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt
new file mode 100644
index 0000000..0c1b417
--- /dev/null
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import android.content.Context
+import android.content.SharedPreferences
+import android.os.Build
+import android.util.Log
+import androidx.core.content.ContextCompat
+import java.io.File
+
+/**
+ * [SharedPreferences] based storage.
+ *
+ * The backup and restore is handled by [BackupRestoreFileArchiver] to achieve forward-compatibility
+ * just like `PersistentBackupAgentHelper`.
+ *
+ * Simple file based backup and restore is not safe, which incurs multi-thread file writes in
+ * SharedPreferences file. Additionally, SharedPreferences has in-memory state, so reload is needed.
+ * However, there is no public reload API on SharedPreferences and listeners are not notified in
+ * current private implementation. As such, an intermediate SharedPreferences file is introduced for
+ * backup and restore.
+ *
+ * Note that existing entries in the SharedPreferences will NOT be deleted before restore.
+ *
+ * @param context Context to get SharedPreferences
+ * @param name Name of the SharedPreferences
+ * @param mode Operating mode, see [Context.getSharedPreferences]
+ * @param verbose Verbose logging on key/value pairs during backup/restore. Enable for dev only!
+ * @param filter Filter of key/value pairs for backup and restore.
+ */
+class SharedPreferencesStorage
+@JvmOverloads
+constructor(
+ context: Context,
+ override val name: String,
+ mode: Int,
+ private val verbose: Boolean = (Build.TYPE == "eng"),
+ private val filter: (String, Any?) -> Boolean = { _, _ -> true },
+) :
+ BackupRestoreFileStorage(context, context.getSharedPreferencesFilePath(name)),
+ KeyedObservable<String> by KeyedDataObservable() {
+
+ private val sharedPreferences = context.getSharedPreferences(name, mode)
+
+ /** Name of the intermediate SharedPreferences. */
+ private val intermediateName: String
+ get() = "_br_$name"
+
+ private val intermediateSharedPreferences: SharedPreferences
+ get() {
+ // use MODE_MULTI_PROCESS to ensure a reload
+ return context.getSharedPreferences(intermediateName, Context.MODE_MULTI_PROCESS)
+ }
+
+ private val sharedPreferencesListener =
+ SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
+ if (key != null) {
+ notifyChange(key, ChangeReason.UPDATE)
+ } else {
+ // On Android >= R, SharedPreferences.Editor.clear() will trigger this case
+ notifyChange(ChangeReason.DELETE)
+ }
+ }
+
+ init {
+ // listener is weakly referenced, so unregister is optional
+ sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferencesListener)
+ }
+
+ override val backupFile: File
+ // use a different file to avoid multi-thread file write
+ get() = context.getSharedPreferencesFile(intermediateName)
+
+ override fun prepareBackup(file: File) {
+ val editor = intermediateSharedPreferences.merge(sharedPreferences.all, "Backup")
+ // commit to ensure data is write to disk synchronously
+ if (!editor.commit()) {
+ Log.w(LOG_TAG, "[$name] fail to commit")
+ }
+ }
+
+ override fun onBackupFinished(file: File) {
+ intermediateSharedPreferences.delete(intermediateName)
+ }
+
+ override fun onRestoreFinished(file: File) {
+ // Unregister listener to avoid notify observer during restore because there might be
+ // dependency between keys. BackupRestoreStorageManager.onRestoreFinished will notify
+ // observers consistently once restore finished.
+ sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferencesListener)
+ val restored = intermediateSharedPreferences
+ val editor = sharedPreferences.merge(restored.all, "Restore")
+ editor.apply() // apply to avoid blocking
+ sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferencesListener)
+ // clear the intermediate SharedPreferences
+ restored.delete(intermediateName)
+ }
+
+ private fun SharedPreferences.delete(name: String) {
+ if (deleteSharedPreferences(name)) {
+ Log.i(LOG_TAG, "SharedPreferences $name deleted")
+ } else {
+ edit().clear().apply()
+ }
+ }
+
+ private fun deleteSharedPreferences(name: String): Boolean =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ context.deleteSharedPreferences(name)
+ } else {
+ false
+ }
+
+ private fun SharedPreferences.merge(
+ entries: Map<String, Any?>,
+ operation: String
+ ): SharedPreferences.Editor {
+ val editor = edit()
+ for ((key, value) in entries) {
+ if (!filter.invoke(key, value)) {
+ if (verbose) Log.v(LOG_TAG, "[$name] $operation skips $key=$value")
+ continue
+ }
+ when (value) {
+ is Boolean -> {
+ editor.putBoolean(key, value)
+ if (verbose) Log.v(LOG_TAG, "[$name] $operation Boolean $key=$value")
+ }
+ is Float -> {
+ editor.putFloat(key, value)
+ if (verbose) Log.v(LOG_TAG, "[$name] $operation Float $key=$value")
+ }
+ is Int -> {
+ editor.putInt(key, value)
+ if (verbose) Log.v(LOG_TAG, "[$name] $operation Int $key=$value")
+ }
+ is Long -> {
+ editor.putLong(key, value)
+ if (verbose) Log.v(LOG_TAG, "[$name] $operation Long $key=$value")
+ }
+ is String -> {
+ editor.putString(key, value)
+ if (verbose) Log.v(LOG_TAG, "[$name] $operation String $key=$value")
+ }
+ is Set<*> -> {
+ val nonString = value.firstOrNull { it !is String }
+ if (nonString != null) {
+ Log.e(
+ LOG_TAG,
+ "[$name] $operation StringSet $key=$value" +
+ " but non string found: $nonString (${nonString.javaClass})",
+ )
+ } else {
+ @Suppress("UNCHECKED_CAST") editor.putStringSet(key, value as Set<String>)
+ if (verbose) Log.v(LOG_TAG, "[$name] $operation StringSet $key=$value")
+ }
+ }
+ else -> {
+ Log.e(
+ LOG_TAG,
+ "[$name] $operation $key=$value, unknown type: ${value?.javaClass}"
+ )
+ }
+ }
+ }
+ return editor
+ }
+
+ companion object {
+ private fun Context.getSharedPreferencesFilePath(name: String): String {
+ val file = getSharedPreferencesFile(name)
+ return file.relativeTo(ContextCompat.getDataDir(this)!!).toString()
+ }
+
+ /** Returns the absolute path of shared preferences file. */
+ @JvmStatic
+ fun Context.getSharedPreferencesFile(name: String): File {
+ // ContextImpl.getSharedPreferencesPath is private
+ return File(getSharedPreferencesDir(), "$name.xml")
+ }
+
+ private fun Context.getSharedPreferencesDir() = File(dataDirCompat, "shared_prefs")
+ }
+}
diff --git a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
index 18e8fc3..f47041d 100644
--- a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
+++ b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
@@ -85,7 +85,7 @@
*/
@RequiresApi(Build.VERSION_CODES.M)
public static void sendShowAdminSupportDetailsIntent(Context context, EnforcedAdmin admin) {
- final Intent intent = getShowAdminSupportDetailsIntent(context, admin);
+ final Intent intent = getShowAdminSupportDetailsIntent(admin);
int targetUserId = UserHandle.myUserId();
if (admin != null) {
if (admin.user != null
@@ -98,9 +98,16 @@
}
/**
- * Gets the intent to trigger the {@code android.settings.ShowAdminSupportDetailsDialog}.
+ * @deprecated No context needed. Use {@link #getShowAdminSupportDetailsIntent(EnforcedAdmin)}.
*/
public static Intent getShowAdminSupportDetailsIntent(Context context, EnforcedAdmin admin) {
+ return getShowAdminSupportDetailsIntent(admin);
+ }
+
+ /**
+ * Gets the intent to trigger the {@code android.settings.ShowAdminSupportDetailsDialog}.
+ */
+ public static Intent getShowAdminSupportDetailsIntent(EnforcedAdmin admin) {
final Intent intent = new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
if (admin != null) {
if (admin.component != null) {
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 335725c..c755623 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@
allprojects {
extra["androidTop"] = androidTop
- extra["jetpackComposeVersion"] = "1.7.0-alpha03"
+ extra["jetpackComposeVersion"] = "1.7.0-alpha04"
}
subprojects {
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 609a82e..ff2a1e8 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,7 +15,7 @@
#
[versions]
-agp = "8.2.2"
+agp = "8.3.0"
compose-compiler = "1.5.10"
dexmaker-mockito = "2.28.3"
jvm = "17"
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.6-bin.zip b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.6-bin.zip
new file mode 100644
index 0000000..5c96347
--- /dev/null
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.6-bin.zip
Binary files differ
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
index 516749d..50ff9df 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
@@ -16,8 +16,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
-networkTimeout=10000
-validateDistributionUrl=true
+distributionUrl=gradle-8.6-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenPathManager.kt
similarity index 69%
rename from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
rename to packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenPathManager.kt
index f5fba7f..d590760 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenPathManager.kt
@@ -17,28 +17,25 @@
package com.android.settingslib.spa.screenshot.util
import androidx.test.platform.app.InstrumentationRegistry
-import platform.test.screenshot.GoldenImagePathManager
+import platform.test.screenshot.GoldenPathManager
import platform.test.screenshot.PathConfig
-/** A [GoldenImagePathManager] that should be used for all Settings screenshot tests. */
-class SettingsGoldenImagePathManager(
- pathConfig: PathConfig,
- assetsPathRelativeToBuildRoot: String
-) :
- GoldenImagePathManager(
+/** A [GoldenPathManager] that should be used for all Settings screenshot tests. */
+class SettingsGoldenPathManager(pathConfig: PathConfig, assetsPathRelativeToBuildRoot: String) :
+ GoldenPathManager(
appContext = InstrumentationRegistry.getInstrumentation().context,
assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
deviceLocalPath =
- InstrumentationRegistry.getInstrumentation()
- .targetContext
- .filesDir
- .absolutePath
- .toString() + "/settings_screenshots",
+ InstrumentationRegistry.getInstrumentation()
+ .targetContext
+ .filesDir
+ .absolutePath
+ .toString() + "/settings_screenshots",
pathConfig = pathConfig,
) {
override fun toString(): String {
// This string is appended to all actual/expected screenshots on the device, so make sure
// it is a static value.
- return "SettingsGoldenImagePathManager"
+ return "SettingsGoldenPathManager"
}
}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
index ae85675..16f6b5e 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
@@ -44,7 +44,7 @@
private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
private val screenshotRule =
ScreenshotTestRule(
- SettingsGoldenImagePathManager(
+ SettingsGoldenPathManager(
getEmulatedDevicePathConfig(emulationSpec),
assetsPathRelativeToBuildRoot
)
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index a193a2f..f2b9235 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -57,13 +57,13 @@
api("androidx.slice:slice-builders:1.1.0-alpha02")
api("androidx.slice:slice-core:1.1.0-alpha02")
api("androidx.slice:slice-view:1.1.0-alpha02")
- api("androidx.compose.material3:material3:1.3.0-alpha01")
+ api("androidx.compose.material3:material3:1.3.0-alpha02")
api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
api("androidx.lifecycle:lifecycle-livedata-ktx")
api("androidx.lifecycle:lifecycle-runtime-compose")
- api("androidx.navigation:navigation-compose:2.8.0-alpha02")
+ api("androidx.navigation:navigation-compose:2.8.0-alpha03")
api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
api("com.google.android.material:material:1.7.0-alpha03")
debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
index fc8de80..0a469b8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
@@ -47,7 +47,7 @@
onCheckedChange = model.onCheckedChange,
paddingStart = 20.dp,
paddingEnd = 20.dp,
- paddingVertical = 18.dp,
+ paddingVertical = 24.dp,
)
}
}
@@ -55,7 +55,7 @@
@Preview
@Composable
-fun MainSwitchPreferencePreview() {
+private fun MainSwitchPreferencePreview() {
SettingsTheme {
Column {
MainSwitchPreference(object : SwitchPreferenceModel {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreference.kt
index 9866023..3f74ed5 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreference.kt
@@ -36,7 +36,7 @@
TwoTargetPreference(
title = title,
summary = summary,
- onClick = onClick,
+ primaryOnClick = onClick,
icon = icon,
) {
IconButton(onClick = onButtonClick) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
index 3216e37..3f68804 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
@@ -34,7 +34,8 @@
internal fun TwoTargetPreference(
title: String,
summary: () -> String,
- onClick: () -> Unit,
+ primaryEnabled: () -> Boolean = { true },
+ primaryOnClick: (() -> Unit)?,
icon: @Composable (() -> Unit)? = null,
widget: @Composable () -> Unit,
) {
@@ -50,7 +51,8 @@
override val title = title
override val summary = summary
override val icon = icon
- override val onClick = onClick
+ override val enabled = primaryEnabled
+ override val onClick = primaryOnClick
}
)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
index 7eed745..8b546b4 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
@@ -24,13 +24,15 @@
fun TwoTargetSwitchPreference(
model: SwitchPreferenceModel,
icon: @Composable (() -> Unit)? = null,
- onClick: () -> Unit,
+ primaryEnabled: () -> Boolean = { true },
+ primaryOnClick: (() -> Unit)?,
) {
EntryHighlight {
TwoTargetPreference(
title = model.title,
summary = model.summary,
- onClick = onClick,
+ primaryEnabled = primaryEnabled,
+ primaryOnClick = primaryOnClick,
icon = icon,
) {
SettingsSwitch(
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreferenceTest.kt
index 3455851..0acf287 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreferenceTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreferenceTest.kt
@@ -23,6 +23,7 @@
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotEnabled
import androidx.compose.ui.test.assertIsOff
import androidx.compose.ui.test.assertIsOn
import androidx.compose.ui.test.isToggleable
@@ -46,7 +47,7 @@
TestTwoTargetSwitchPreference(changeable = true)
}
- composeTestRule.onNodeWithText("TwoTargetSwitchPreference").assertIsDisplayed()
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
}
@Test
@@ -79,7 +80,7 @@
}
@Test
- fun clickable_canBeClick() {
+ fun clickable_primaryEnabled_canBeClick() {
var clicked = false
composeTestRule.setContent {
TestTwoTargetSwitchPreference(changeable = false) {
@@ -87,26 +88,54 @@
}
}
- composeTestRule.onNodeWithText("TwoTargetSwitchPreference").performClick()
+ composeTestRule.onNodeWithText(TITLE).performClick()
assertThat(clicked).isTrue()
}
-}
-@Composable
-private fun TestTwoTargetSwitchPreference(
- changeable: Boolean,
- onClick: () -> Unit = {},
-) {
- var checked by rememberSaveable { mutableStateOf(false) }
- TwoTargetSwitchPreference(
- model = remember {
- object : SwitchPreferenceModel {
- override val title = "TwoTargetSwitchPreference"
- override val checked = { checked }
- override val changeable = { changeable }
- override val onCheckedChange = { newChecked: Boolean -> checked = newChecked }
+ @Test
+ fun clickable_primaryNotEnabled_assertIsNotEnabled() {
+ composeTestRule.setContent {
+ TestTwoTargetSwitchPreference(changeable = false, primaryEnabled = false)
+ }
+
+ composeTestRule.onNodeWithText(TITLE).assertIsNotEnabled()
+ }
+
+ @Test
+ fun clickable_primaryNotEnabled_canNotBeClick() {
+ var clicked = false
+ composeTestRule.setContent {
+ TestTwoTargetSwitchPreference(changeable = false, primaryEnabled = false) {
+ clicked = true
}
- },
- onClick = onClick,
- )
+ }
+
+ composeTestRule.onNodeWithText(TITLE).performClick()
+ assertThat(clicked).isFalse()
+ }
+
+ @Composable
+ private fun TestTwoTargetSwitchPreference(
+ changeable: Boolean,
+ primaryEnabled: Boolean = true,
+ primaryOnClick: () -> Unit = {},
+ ) {
+ var checked by rememberSaveable { mutableStateOf(false) }
+ TwoTargetSwitchPreference(
+ model = remember {
+ object : SwitchPreferenceModel {
+ override val title = TITLE
+ override val checked = { checked }
+ override val changeable = { changeable }
+ override val onCheckedChange = { newChecked: Boolean -> checked = newChecked }
+ }
+ },
+ primaryEnabled = { primaryEnabled },
+ primaryOnClick = primaryOnClick,
+ )
+ }
+
+ private companion object {
+ const val TITLE = "Title"
+ }
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
index bea14c3..5c2d770 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
@@ -38,6 +38,6 @@
override val onCheckedChange = onCheckedChange
},
icon = { AppIcon(record.app, SettingsDimension.appIconItemSize) },
- onClick = onClick,
+ primaryOnClick = onClick,
)
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt
index ac85dd4..389b3c1 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt
@@ -54,14 +54,27 @@
return
}
val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
- val restrictedSwitchModel = remember(restrictedMode) {
+ val restrictedModel = remember(restrictedMode) {
RestrictedPreferenceModel(model, restrictedMode)
}
- restrictedSwitchModel.RestrictionWrapper {
- Preference(restrictedSwitchModel)
+ restrictedModel.RestrictionWrapper {
+ Preference(restrictedModel)
}
}
+internal fun RestrictedMode?.restrictEnabled(enabled: () -> Boolean) = when (this) {
+ NoRestricted -> enabled
+ else -> ({ false })
+}
+
+internal fun <T> RestrictedMode?.restrictOnClick(onClick: T): T? = when (this) {
+ NoRestricted -> onClick
+ // Need to passthrough onClick for clickable semantics, although since enabled is false so
+ // this will not be called.
+ BaseUserRestricted -> onClick
+ else -> null
+}
+
private class RestrictedPreferenceModel(
model: PreferenceModel,
private val restrictedMode: RestrictedMode?,
@@ -69,19 +82,8 @@
override val title = model.title
override val summary = model.summary
override val icon = model.icon
-
- override val enabled = when (restrictedMode) {
- NoRestricted -> model.enabled
- else -> ({ false })
- }
-
- override val onClick = when (restrictedMode) {
- NoRestricted -> model.onClick
- // Need to passthrough onClick for clickable semantics, although since enabled is false so
- // this will not be called.
- BaseUserRestricted -> model.onClick
- else -> null
- }
+ override val enabled = restrictedMode.restrictEnabled(model.enabled)
+ override val onClick = restrictedMode.restrictOnClick(model.onClick)
@Composable
fun RestrictionWrapper(content: @Composable () -> Unit) {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
index aba3460..5dfecb0 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
@@ -60,18 +60,9 @@
is BlockedByEcm -> model.checked
}
- override val changeable = if (restrictedMode is NoRestricted) model.changeable else ({ false })
+ override val changeable = restrictedMode.restrictEnabled(model.changeable)
- override val onCheckedChange = when (restrictedMode) {
- null -> null
- is NoRestricted -> model.onCheckedChange
- // Need to passthrough onCheckedChange for toggleable semantics, although since changeable
- // is false so this will not be called.
- is BaseUserRestricted -> model.onCheckedChange
- // Pass null since semantics ToggleableState is provided in RestrictionWrapper.
- is BlockedByAdmin -> null
- is BlockedByEcm -> null
- }
+ override val onCheckedChange = restrictedMode.restrictOnClick(model.onCheckedChange)
@Composable
fun RestrictionWrapper(content: @Composable () -> Unit) {
@@ -116,13 +107,12 @@
companion object {
@Composable
- fun RestrictionsProviderFactory.RestrictedSwitchWrapper(
+ fun RestrictedSwitchWrapper(
model: SwitchPreferenceModel,
- restrictions: Restrictions,
+ restrictedMode: RestrictedMode?,
content: @Composable (SwitchPreferenceModel) -> Unit,
) {
val context = LocalContext.current
- val restrictedMode = rememberRestrictedMode(restrictions).value
val restrictedSwitchPreferenceModel = remember(restrictedMode) {
RestrictedSwitchPreferenceModel(context, model, restrictedMode)
}
@@ -131,6 +121,15 @@
}
}
+ @Composable
+ fun RestrictionsProviderFactory.RestrictedSwitchWrapper(
+ model: SwitchPreferenceModel,
+ restrictions: Restrictions,
+ content: @Composable (SwitchPreferenceModel) -> Unit,
+ ) {
+ RestrictedSwitchWrapper(model, rememberRestrictedMode(restrictions).value, content)
+ }
+
fun getSummary(
context: Context,
restrictedModeSupplier: () -> RestrictedMode?,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt
new file mode 100644
index 0000000..e100773
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.preference
+
+import androidx.annotation.VisibleForTesting
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
+import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode
+import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreferenceModel.Companion.RestrictedSwitchWrapper
+
+@Composable
+fun RestrictedTwoTargetSwitchPreference(
+ model: SwitchPreferenceModel,
+ icon: @Composable (() -> Unit)? = null,
+ restrictions: Restrictions,
+ primaryEnabled: () -> Boolean = { true },
+ primaryOnClick: (() -> Unit)?,
+) {
+ RestrictedTwoTargetSwitchPreference(
+ model = model,
+ icon = icon,
+ primaryEnabled = primaryEnabled,
+ primaryOnClick = primaryOnClick,
+ restrictions = restrictions,
+ restrictionsProviderFactory = ::RestrictionsProviderImpl,
+ )
+}
+
+@VisibleForTesting
+@Composable
+internal fun RestrictedTwoTargetSwitchPreference(
+ model: SwitchPreferenceModel,
+ icon: @Composable (() -> Unit)? = null,
+ primaryEnabled: () -> Boolean = { true },
+ primaryOnClick: (() -> Unit)?,
+ restrictions: Restrictions,
+ restrictionsProviderFactory: RestrictionsProviderFactory,
+) {
+ if (restrictions.isEmpty()) {
+ TwoTargetSwitchPreference(model, icon, primaryEnabled, primaryOnClick)
+ return
+ }
+ val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
+ RestrictedSwitchWrapper(model, restrictedMode) { restrictedModel ->
+ TwoTargetSwitchPreference(
+ model = restrictedModel,
+ icon = icon,
+ primaryEnabled = restrictedMode.restrictEnabled(primaryEnabled),
+ primaryOnClick = restrictedMode.restrictOnClick(primaryOnClick),
+ )
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreferenceTest.kt
new file mode 100644
index 0000000..bdff89f
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreferenceTest.kt
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.preference
+
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.isOff
+import androidx.compose.ui.test.isOn
+import androidx.compose.ui.test.isToggleable
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByAdmin
+import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByEcm
+import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RestrictedTwoTargetSwitchPreferenceTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private val fakeBlockedByAdmin = FakeBlockedByAdmin()
+ private val fakeBlockedByEcm = FakeBlockedByEcm()
+
+ private val fakeRestrictionsProvider = FakeRestrictionsProvider()
+
+ private val switchPreferenceModel = object : SwitchPreferenceModel {
+ override val title = TITLE
+ private val checkedState = mutableStateOf(true)
+ override val checked = { checkedState.value }
+ override val onCheckedChange: (Boolean) -> Unit = { checkedState.value = it }
+ }
+
+ @Test
+ fun whenRestrictionsKeysIsEmpty_enabled() {
+ val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+ composeTestRule.onNode(isOn()).assertIsDisplayed()
+ }
+
+ @Test
+ fun whenRestrictionsKeysIsEmpty_toggleable() {
+ val restrictions = Restrictions(userId = USER_ID, keys = emptyList())
+
+ setContent(restrictions)
+ composeTestRule.onNode(isToggleable()).performClick()
+
+ composeTestRule.onNode(isOff()).assertIsDisplayed()
+ }
+
+ @Test
+ fun whenNoRestricted_enabled() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+ composeTestRule.onNode(isOn()).assertIsDisplayed().assertIsEnabled()
+ }
+
+ @Test
+ fun whenNoRestricted_toggleable() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = NoRestricted
+
+ setContent(restrictions)
+ composeTestRule.onNode(isToggleable()).performClick()
+
+ composeTestRule.onNode(isOff()).assertIsDisplayed()
+ }
+
+ @Test
+ fun whenBaseUserRestricted_disabled() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsNotEnabled()
+ composeTestRule.onNode(isOff()).assertIsDisplayed()
+ }
+
+ @Test
+ fun whenBaseUserRestricted_notToggleable() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = BaseUserRestricted
+
+ setContent(restrictions)
+ composeTestRule.onNode(isToggleable()).performClick()
+
+ composeTestRule.onNode(isOff()).assertIsDisplayed()
+ }
+
+ @Test
+ fun whenBlockedByAdmin_disabled() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+ composeTestRule.onNodeWithText(FakeBlockedByAdmin.SUMMARY).assertIsDisplayed()
+ composeTestRule.onNode(isOn()).assertIsDisplayed().assertIsEnabled()
+ }
+
+ @Test
+ fun whenBlockedByAdmin_clickPrimary() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+ setContent(restrictions)
+ composeTestRule.onNodeWithText(TITLE).performClick()
+
+ assertThat(fakeBlockedByAdmin.sendShowAdminSupportDetailsIntentIsCalled).isTrue()
+ }
+
+ @Test
+ fun whenBlockedByAdmin_clickSwitch() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin
+
+ setContent(restrictions)
+ composeTestRule.onNode(isToggleable()).performClick()
+
+ assertThat(fakeBlockedByAdmin.sendShowAdminSupportDetailsIntentIsCalled).isTrue()
+ }
+
+ @Test
+ fun whenBlockedByEcm_disabled() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = fakeBlockedByEcm
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+ composeTestRule.onNodeWithText(FakeBlockedByEcm.SUMMARY).assertIsDisplayed()
+ composeTestRule.onNode(isOn()).assertIsDisplayed()
+ }
+
+ @Test
+ fun whenBlockedByEcm_click() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = fakeBlockedByEcm
+
+ setContent(restrictions)
+ composeTestRule.onRoot().performClick()
+
+ assertThat(fakeBlockedByEcm.showRestrictedSettingsDetailsIsCalled).isTrue()
+ }
+
+ private fun setContent(restrictions: Restrictions, primaryOnClick: (() -> Unit)? = {}) {
+ composeTestRule.setContent {
+ RestrictedTwoTargetSwitchPreference(
+ model = switchPreferenceModel,
+ primaryOnClick = primaryOnClick,
+ restrictions = restrictions,
+ ) { _, _ ->
+ fakeRestrictionsProvider
+ }
+ }
+ }
+
+ private companion object {
+ const val TITLE = "Title"
+ const val USER_ID = 0
+ const val RESTRICTION_KEY = "restriction_key"
+ }
+}
diff --git a/packages/SettingsLib/res/layout/dialog_with_icon.xml b/packages/SettingsLib/res/layout/dialog_with_icon.xml
index 3586dcb..b21895b 100644
--- a/packages/SettingsLib/res/layout/dialog_with_icon.xml
+++ b/packages/SettingsLib/res/layout/dialog_with_icon.xml
@@ -35,12 +35,14 @@
android:id="@+id/dialog_with_icon_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:hyphenationFrequency="fullFast"
android:gravity="center"
style="@style/DialogWithIconTitle"/>
<TextView
android:id="@+id/dialog_with_icon_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:hyphenationFrequency="fullFast"
android:gravity="center"
style="@style/TextAppearanceSmall"/>
diff --git a/packages/SettingsLib/res/values-fr-feminine/strings.xml b/packages/SettingsLib/res/values-fr-feminine/strings.xml
new file mode 100644
index 0000000..377c42c
--- /dev/null
+++ b/packages/SettingsLib/res/values-fr-feminine/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"L\'association vous permet d\'accéder à vos contacts et à l\'historique des appels lorsque vous êtes connectée."</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-fr-masculine/strings.xml b/packages/SettingsLib/res/values-fr-masculine/strings.xml
new file mode 100644
index 0000000..5f7d58a
--- /dev/null
+++ b/packages/SettingsLib/res/values-fr-masculine/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"L\'association vous permet d\'accéder à vos contacts et à l\'historique des appels lorsque vous êtes connecté."</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-fr-neuter/strings.xml b/packages/SettingsLib/res/values-fr-neuter/strings.xml
new file mode 100644
index 0000000..6970587
--- /dev/null
+++ b/packages/SettingsLib/res/values-fr-neuter/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"L\'association vous permet d\'accéder à vos contacts et à l\'historique des appels lorsque vous êtes connecté·e."</string>
+</resources>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index e3c1f65b..3353374 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -457,8 +457,7 @@
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"נעקף על ידי <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – הטעינה הושהתה כדי להגן על הסוללה"</string>
- <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) -->
- <skip />
+ <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – צריך לבדוק את אביזר הטעינה"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"הזמן הנותר: בערך <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
<string name="power_discharging_duration" msgid="1076561255466053220">"הזמן הנותר: בערך <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
<string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"הזמן הנותר על סמך השימוש שלך: בערך <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index 8aac7f1..c540945 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -457,8 +457,7 @@
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Jaunā preference: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> — <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> — uzlāde apturēta, lai aizsargātu akumulatoru"</string>
- <!-- no translation found for power_incompatible_charging_settings_home_page (1322050766135126880) -->
- <skip />
+ <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> — pārbaudiet uzlādes piederumu"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"Aptuvenais atlikušais laiks: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
<string name="power_discharging_duration" msgid="1076561255466053220">"Aptuvenais atlikušais laiks: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
<string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Ņemot vērā lietojumu, atlikušais laiks: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 256a71b..22162bb 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -457,7 +457,7 @@
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overstyres av <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_charging_on_hold_settings_home_page" msgid="7690464049464805856">"<xliff:g id="LEVEL">%1$s</xliff:g> – Ladingen er satt på vent for å beskytte batteriet"</string>
- <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – Sjekk ladetilbehøret"</string>
+ <string name="power_incompatible_charging_settings_home_page" msgid="1322050766135126880">"<xliff:g id="LEVEL">%1$s</xliff:g> – sjekk ladetilbehøret"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"Omtrent <xliff:g id="TIME_REMAINING">%1$s</xliff:g> igjen"</string>
<string name="power_discharging_duration" msgid="1076561255466053220">"Omtrent <xliff:g id="TIME_REMAINING">%1$s</xliff:g> igjen (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
<string name="power_remaining_duration_only_enhanced" msgid="2527842780666073218">"Omtrent <xliff:g id="TIME_REMAINING">%1$s</xliff:g> igjen basert på bruken din"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 1150ac1..87b4c0f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -785,30 +785,6 @@
return false;
}
- /** Whether to show the wireless charging notification. */
- public static boolean shouldShowWirelessChargingNotification(
- @NonNull Context context, @NonNull String tag) {
- try {
- return shouldShowWirelessChargingNotificationInternal(context, tag);
- } catch (Exception e) {
- Log.e(tag, "shouldShowWirelessChargingNotification()", e);
- return false;
- }
- }
-
- /** Stores the timestamp of the wireless charging notification. */
- public static void updateWirelessChargingNotificationTimestamp(
- @NonNull Context context, long timestamp, @NonNull String tag) {
- try {
- Secure.putLong(
- context.getContentResolver(),
- WIRELESS_CHARGING_NOTIFICATION_TIMESTAMP,
- timestamp);
- } catch (Exception e) {
- Log.e(tag, "setWirelessChargingNotificationTimestamp()", e);
- }
- }
-
/** Whether to show the wireless charging warning in Settings. */
public static boolean shouldShowWirelessChargingWarningTip(
@NonNull Context context, @NonNull String tag) {
@@ -833,37 +809,4 @@
Log.e(tag, "setWirelessChargingWarningEnabled()", e);
}
}
-
- private static boolean shouldShowWirelessChargingNotificationInternal(
- @NonNull Context context, @NonNull String tag) {
- final long lastNotificationTimeMillis =
- Secure.getLong(
- context.getContentResolver(),
- WIRELESS_CHARGING_NOTIFICATION_TIMESTAMP,
- WIRELESS_CHARGING_DEFAULT_TIMESTAMP);
- if (isWirelessChargingNotificationDisabled(lastNotificationTimeMillis)) {
- return false;
- }
- if (isInitialWirelessChargingNotification(lastNotificationTimeMillis)) {
- updateWirelessChargingNotificationTimestamp(context, System.currentTimeMillis(), tag);
- updateWirelessChargingWarningEnabled(context, /* enabled= */ true, tag);
- return true;
- }
- final long durationMillis = System.currentTimeMillis() - lastNotificationTimeMillis;
- final boolean show = durationMillis > WIRELESS_CHARGING_NOTIFICATION_THRESHOLD_MILLIS;
- Log.d(tag, "shouldShowWirelessChargingNotification = " + show);
- if (show) {
- updateWirelessChargingNotificationTimestamp(context, System.currentTimeMillis(), tag);
- updateWirelessChargingWarningEnabled(context, /* enabled= */ true, tag);
- }
- return show;
- }
-
- private static boolean isWirelessChargingNotificationDisabled(long lastNotificationTimeMillis) {
- return lastNotificationTimeMillis == Long.MIN_VALUE;
- }
-
- private static boolean isInitialWirelessChargingNotification(long lastNotificationTimeMillis) {
- return lastNotificationTimeMillis == WIRELESS_CHARGING_DEFAULT_TIMESTAMP;
- }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index bd27c89..1118efc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -81,11 +81,13 @@
public static final int BROADCAST_STATE_UNKNOWN = 0;
public static final int BROADCAST_STATE_ON = 1;
public static final int BROADCAST_STATE_OFF = 2;
+
@Retention(RetentionPolicy.SOURCE)
@IntDef(
prefix = {"BROADCAST_STATE_"},
value = {BROADCAST_STATE_UNKNOWN, BROADCAST_STATE_ON, BROADCAST_STATE_OFF})
public @interface BroadcastState {}
+
private static final String SETTINGS_PKG = "com.android.settings";
private static final String TAG = "LocalBluetoothLeBroadcast";
private static final boolean DEBUG = BluetoothUtils.D;
@@ -1068,7 +1070,7 @@
return;
}
int fallbackActiveGroupId = getFallbackActiveGroupId();
- if (targetCachedDevice.getGroupId() == fallbackActiveGroupId) {
+ if (getGroupId(targetCachedDevice) == fallbackActiveGroupId) {
Log.d(
TAG,
"Skip updateFallbackActiveDeviceIfNeeded, already is fallback: "
@@ -1091,6 +1093,23 @@
BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
}
+ private int getGroupId(CachedBluetoothDevice cachedDevice) {
+ int groupId = cachedDevice.getGroupId();
+ String anonymizedAddress = cachedDevice.getDevice().getAnonymizedAddress();
+ if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+ Log.d(TAG, "getGroupId by CSIP profile for device: " + anonymizedAddress);
+ return groupId;
+ }
+ for (LocalBluetoothProfile profile : cachedDevice.getProfiles()) {
+ if (profile instanceof LeAudioProfile) {
+ Log.d(TAG, "getGroupId by LEA profile for device: " + anonymizedAddress);
+ return ((LeAudioProfile) profile).getGroupId(cachedDevice.getDevice());
+ }
+ }
+ Log.d(TAG, "getGroupId return invalid id for device: " + anonymizedAddress);
+ return BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
+ }
+
private void notifyBroadcastStateChange(@BroadcastState int state) {
if (!mContext.getPackageName().equals(SETTINGS_PKG)) {
Log.d(TAG, "Skip notifyBroadcastStateChange, not triggered by Settings.");
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
index ff4d4dd..2b8c2dd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
@@ -37,6 +37,23 @@
*/
// TODO - b/293578081: Remove once PackageNotAvailableException is propagated to library clients.
/* package */ final class NoOpInfoMediaManager extends InfoMediaManager {
+ /**
+ * Placeholder routing session to return as active session of {@link NoOpInfoMediaManager}.
+ *
+ * <p>Returning this routing session avoids crashes in {@link InfoMediaManager} and maintains
+ * the same client-facing behaviour as if no routing session was found for the target package
+ * name.
+ *
+ * <p>Volume and max volume are set to {@code -1} to emulate a non-existing routing session in
+ * {@link #getSessionVolume()} and {@link #getSessionVolumeMax()}.
+ */
+ private static final RoutingSessionInfo PLACEHOLDER_SESSION =
+ new RoutingSessionInfo.Builder(
+ /* id */ "FAKE_ROUTING_SESSION", /* clientPackageName */ "")
+ .addSelectedRoute(/* routeId */ "FAKE_SELECTED_ROUTE_ID")
+ .setVolumeMax(-1)
+ .setVolume(-1)
+ .build();
NoOpInfoMediaManager(
Context context,
@@ -118,7 +135,7 @@
@NonNull
@Override
protected List<RoutingSessionInfo> getRoutingSessionsForPackage() {
- return Collections.emptyList();
+ return List.of(PLACEHOLDER_SESSION);
}
@Nullable
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt b/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt
index a5c63be..7e3f38b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt
@@ -18,23 +18,18 @@
import android.media.AudioDeviceAttributes
import android.media.Spatializer
-import androidx.concurrent.futures.DirectExecutor
import kotlin.coroutines.CoroutineContext
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
interface SpatializerRepository {
- /** Returns true when head tracking is enabled and false the otherwise. */
- val isHeadTrackingAvailable: StateFlow<Boolean>
+ /**
+ * Returns true when head tracking is available for the [audioDeviceAttributes] and false the
+ * otherwise.
+ */
+ suspend fun isHeadTrackingAvailableForDevice(
+ audioDeviceAttributes: AudioDeviceAttributes
+ ): Boolean
/**
* Returns true when Spatial audio feature is supported for the [audioDeviceAttributes] and
@@ -65,22 +60,14 @@
class SpatializerRepositoryImpl(
private val spatializer: Spatializer,
- coroutineScope: CoroutineScope,
private val backgroundContext: CoroutineContext,
) : SpatializerRepository {
- override val isHeadTrackingAvailable: StateFlow<Boolean> =
- callbackFlow {
- val listener =
- Spatializer.OnHeadTrackerAvailableListener { _, available ->
- launch { send(available) }
- }
- spatializer.addOnHeadTrackerAvailableListener(DirectExecutor.INSTANCE, listener)
- awaitClose { spatializer.removeOnHeadTrackerAvailableListener(listener) }
- }
- .onStart { emit(spatializer.isHeadTrackerAvailable) }
- .flowOn(backgroundContext)
- .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), false)
+ override suspend fun isHeadTrackingAvailableForDevice(
+ audioDeviceAttributes: AudioDeviceAttributes
+ ): Boolean {
+ return withContext(backgroundContext) { spatializer.hasHeadTracker(audioDeviceAttributes) }
+ }
override suspend fun isSpatialAudioAvailableForDevice(
audioDeviceAttributes: AudioDeviceAttributes
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt
index 0347403..5589733 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt
@@ -18,17 +18,17 @@
import android.media.AudioDeviceAttributes
import com.android.settingslib.media.data.repository.SpatializerRepository
-import kotlinx.coroutines.flow.StateFlow
class SpatializerInteractor(private val repository: SpatializerRepository) {
- /** Checks if head tracking is available. */
- val isHeadTrackingAvailable: StateFlow<Boolean>
- get() = repository.isHeadTrackingAvailable
-
+ /** Checks if spatial audio is available. */
suspend fun isSpatialAudioAvailable(audioDeviceAttributes: AudioDeviceAttributes): Boolean =
repository.isSpatialAudioAvailableForDevice(audioDeviceAttributes)
+ /** Checks if head tracking is available. */
+ suspend fun isHeadTrackingAvailable(audioDeviceAttributes: AudioDeviceAttributes): Boolean =
+ repository.isHeadTrackingAvailableForDevice(audioDeviceAttributes)
+
/** Checks if spatial audio is enabled for the [audioDeviceAttributes]. */
suspend fun isSpatialAudioEnabled(audioDeviceAttributes: AudioDeviceAttributes): Boolean =
repository.getSpatialAudioCompatibleDevices().contains(audioDeviceAttributes)
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
index 840c936..b7108c9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
@@ -236,7 +236,8 @@
// Handle specific carrier config values for the default data SIM
int defaultDataSubId = SubscriptionManager.from(context)
.getDefaultDataSubscriptionId();
- PersistableBundle b = configMgr.getConfigForSubId(defaultDataSubId);
+ PersistableBundle b = configMgr == null ? null
+ : configMgr.getConfigForSubId(defaultDataSubId);
if (b != null) {
config.alwaysShowDataRatIcon = b.getBoolean(
CarrierConfigManager.KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL);
diff --git a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt
index 794cf83..7719c4b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt
@@ -48,6 +48,9 @@
/** Checks if [notificationPolicy] allows media. */
val isMediaAllowed: Flow<Boolean?> = notificationPolicy.map { it?.allowMedia() }
+ /** Checks if [notificationPolicy] allows system sounds. */
+ val isSystemAllowed: Flow<Boolean?> = notificationPolicy.map { it?.allowSystem() }
+
/** Checks if [notificationPolicy] allows ringer. */
val isRingerAllowed: Flow<Boolean?> =
notificationPolicy.map { policy ->
@@ -62,31 +65,29 @@
areAlarmsAllowed.filterNotNull(),
isMediaAllowed.filterNotNull(),
isRingerAllowed.filterNotNull(),
- ) { zenMode, areAlarmsAllowed, isMediaAllowed, isRingerAllowed ->
- if (zenMode.zenMode == Settings.Global.ZEN_MODE_NO_INTERRUPTIONS) {
- return@combine true
+ isSystemAllowed.filterNotNull(),
+ ) { zenMode, areAlarmsAllowed, isMediaAllowed, isRingerAllowed, isSystemAllowed ->
+ when (zenMode.zenMode) {
+ // Everything is muted
+ Settings.Global.ZEN_MODE_NO_INTERRUPTIONS -> return@combine true
+ Settings.Global.ZEN_MODE_ALARMS ->
+ return@combine stream.value == AudioManager.STREAM_RING ||
+ stream.value == AudioManager.STREAM_NOTIFICATION ||
+ stream.value == AudioManager.STREAM_SYSTEM
+ Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS -> {
+ when {
+ stream.value == AudioManager.STREAM_ALARM && !areAlarmsAllowed ->
+ return@combine true
+ stream.value == AudioManager.STREAM_MUSIC && !isMediaAllowed ->
+ return@combine true
+ stream.value == AudioManager.STREAM_SYSTEM && !isSystemAllowed ->
+ return@combine true
+ (stream.value == AudioManager.STREAM_RING ||
+ stream.value == AudioManager.STREAM_NOTIFICATION) && !isRingerAllowed ->
+ return@combine true
+ }
+ }
}
-
- val isNotificationOrRing =
- stream.value == AudioManager.STREAM_RING ||
- stream.value == AudioManager.STREAM_NOTIFICATION
- if (isNotificationOrRing && zenMode.zenMode == Settings.Global.ZEN_MODE_ALARMS) {
- return@combine true
- }
- if (zenMode.zenMode != Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
- return@combine false
- }
-
- if (stream.value == AudioManager.STREAM_ALARM && !areAlarmsAllowed) {
- return@combine true
- }
- if (stream.value == AudioManager.STREAM_MUSIC && !isMediaAllowed) {
- return@combine true
- }
- if (isNotificationOrRing && !isRingerAllowed) {
- return@combine true
- }
-
return@combine false
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
index 53daef1..69c7410 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
@@ -242,6 +242,7 @@
.setMessage(messageResId)
.setNegativeButtonText(R.string.cancel)
.setPositiveButtonText(R.string.next);
+ mCustomDialogHelper.requestFocusOnTitle();
break;
case GRANT_ADMIN_DIALOG:
mEditUserInfoView.setVisibility(View.GONE);
@@ -254,6 +255,7 @@
.setMessage(R.string.user_grant_admin_message)
.setNegativeButtonText(R.string.back)
.setPositiveButtonText(R.string.next);
+ mCustomDialogHelper.requestFocusOnTitle();
if (mIsAdmin == null) {
mCustomDialogHelper.setButtonEnabled(false);
}
@@ -265,6 +267,7 @@
.setTitle(R.string.user_info_settings_title)
.setNegativeButtonText(R.string.back)
.setPositiveButtonText(R.string.done);
+ mCustomDialogHelper.requestFocusOnTitle();
mEditUserInfoView.setVisibility(View.VISIBLE);
mGrantAdminView.setVisibility(View.GONE);
break;
@@ -273,7 +276,6 @@
&& mEditUserPhotoController.getNewUserPhotoDrawable() != null)
? mEditUserPhotoController.getNewUserPhotoDrawable()
: mSavedDrawable;
-
String newName = mUserNameView.getText().toString().trim();
String defaultName = mActivity.getString(R.string.user_new_user_name);
mUserName = !newName.isEmpty() ? newName : defaultName;
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java b/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
index 5201b3d..4cf3bc2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
@@ -23,6 +23,7 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -282,4 +283,13 @@
}
return this;
}
+
+ /**
+ * Requests focus on dialog title when used. Used to let talkback know that the dialog content
+ * is updated and needs to be read from the beginning.
+ */
+ public void requestFocusOnTitle() {
+ mDialogTitle.requestFocus();
+ mDialogTitle.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
index 56b0bf7..1586b8f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
@@ -23,8 +23,8 @@
import com.android.settingslib.volume.shared.model.AudioStreamModel
import com.android.settingslib.volume.shared.model.RingerMode
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
/** Provides audio stream state and an ability to change it */
@@ -43,6 +43,9 @@
streamModel.copy(volume = processVolume(streamModel, ringerMode, isZenMuted))
}
+ val ringerMode: StateFlow<RingerMode>
+ get() = audioRepository.ringerMode
+
suspend fun setVolume(audioStream: AudioStream, volume: Int) =
audioRepository.setVolume(audioStream, volume)
@@ -52,9 +55,14 @@
/** Checks if the volume can be changed via the UI. */
fun canChangeVolume(audioStream: AudioStream): Flow<Boolean> {
return if (audioStream.value == AudioManager.STREAM_NOTIFICATION) {
- getAudioStream(AudioStream(AudioManager.STREAM_RING)).map { !it.isMuted }
+ combine(
+ notificationsSoundPolicyInteractor.isZenMuted(audioStream),
+ getAudioStream(AudioStream(AudioManager.STREAM_RING)).map { it.isMuted },
+ ) { isZenMuted, isRingMuted ->
+ !isZenMuted && !isRingMuted
+ }
} else {
- flowOf(true)
+ notificationsSoundPolicyInteractor.isZenMuted(audioStream).map { !it }
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index 2d07e5d..6f31fad 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -16,9 +16,7 @@
package com.android.settingslib;
import static com.android.settingslib.Utils.STORAGE_MANAGER_ENABLED_PROPERTY;
-import static com.android.settingslib.Utils.WIRELESS_CHARGING_DEFAULT_TIMESTAMP;
import static com.android.settingslib.Utils.shouldShowWirelessChargingWarningTip;
-import static com.android.settingslib.Utils.updateWirelessChargingNotificationTimestamp;
import static com.google.common.truth.Truth.assertThat;
@@ -62,7 +60,6 @@
import org.robolectric.annotation.Implements;
import org.robolectric.shadows.ShadowSettings;
-import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
@@ -547,77 +544,6 @@
}
@Test
- public void shouldShowWirelessChargingNotification_neverSendNotification_returnTrue() {
- updateWirelessChargingNotificationTimestamp(
- mContext, WIRELESS_CHARGING_DEFAULT_TIMESTAMP, TAG);
-
- assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isTrue();
- }
-
- @Test
- public void shouldShowNotification_neverSendNotification_updateTimestampAndEnabledState() {
- updateWirelessChargingNotificationTimestamp(
- mContext, WIRELESS_CHARGING_DEFAULT_TIMESTAMP, TAG);
-
- Utils.shouldShowWirelessChargingNotification(mContext, TAG);
-
- assertThat(getWirelessChargingNotificationTimestamp())
- .isNotEqualTo(WIRELESS_CHARGING_DEFAULT_TIMESTAMP);
- assertThat(shouldShowWirelessChargingWarningTip(mContext, TAG)).isTrue();
- }
-
- @Test
- public void shouldShowWirelessChargingNotification_notificationDisabled_returnFalse() {
- updateWirelessChargingNotificationTimestamp(mContext, CURRENT_TIMESTAMP, TAG);
-
- assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isFalse();
- }
-
- @Test
- public void shouldShowWirelessChargingNotification_withinTimeThreshold_returnFalse() {
- updateWirelessChargingNotificationTimestamp(mContext, CURRENT_TIMESTAMP, TAG);
-
- assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isFalse();
- }
-
- @Test
- public void shouldShowWirelessChargingNotification_exceedTimeThreshold_returnTrue() {
- final long monthAgo = Duration.ofDays(31).toMillis();
- final long timestamp = CURRENT_TIMESTAMP - monthAgo;
- updateWirelessChargingNotificationTimestamp(mContext, timestamp, TAG);
-
- assertThat(Utils.shouldShowWirelessChargingNotification(mContext, TAG)).isTrue();
- }
-
- @Test
- public void shouldShowNotification_exceedTimeThreshold_updateTimestampAndEnabledState() {
- final long monthAgo = Duration.ofDays(31).toMillis();
- final long timestamp = CURRENT_TIMESTAMP - monthAgo;
- updateWirelessChargingNotificationTimestamp(mContext, timestamp, TAG);
-
- Utils.shouldShowWirelessChargingNotification(mContext, TAG);
-
- assertThat(getWirelessChargingNotificationTimestamp()).isNotEqualTo(timestamp);
- assertThat(shouldShowWirelessChargingWarningTip(mContext, TAG)).isTrue();
- }
-
- @Test
- public void updateWirelessChargingNotificationTimestamp_dismissForever_setMinValue() {
- updateWirelessChargingNotificationTimestamp(mContext, Long.MIN_VALUE, TAG);
-
- assertThat(getWirelessChargingNotificationTimestamp()).isEqualTo(Long.MIN_VALUE);
- }
-
- @Test
- public void updateWirelessChargingNotificationTimestamp_notDismissForever_setTimestamp() {
- updateWirelessChargingNotificationTimestamp(mContext, CURRENT_TIMESTAMP, TAG);
-
- assertThat(getWirelessChargingNotificationTimestamp())
- .isNotEqualTo(WIRELESS_CHARGING_DEFAULT_TIMESTAMP);
- assertThat(getWirelessChargingNotificationTimestamp()).isNotEqualTo(Long.MIN_VALUE);
- }
-
- @Test
public void shouldShowWirelessChargingWarningTip_enabled_returnTrue() {
Utils.updateWirelessChargingWarningEnabled(mContext, true, TAG);
@@ -644,11 +570,4 @@
when(mUsbPortStatus.isConnected()).thenReturn(true);
when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[] {complianceWarningType});
}
-
- private long getWirelessChargingNotificationTimestamp() {
- return Settings.Secure.getLong(
- mContext.getContentResolver(),
- Utils.WIRELESS_CHARGING_NOTIFICATION_TIMESTAMP,
- WIRELESS_CHARGING_DEFAULT_TIMESTAMP);
- }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
index fe1529d..9c518de 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
@@ -192,7 +192,7 @@
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
final Executor executor = (command -> new Thread(command).start());
- final BluetoothVolumeControl.Callback callback = (device, volumeOffset) -> {};
+ final BluetoothVolumeControl.Callback callback = new BluetoothVolumeControl.Callback() {};
mProfile.registerCallback(executor, callback);
verify(mService).registerCallback(executor, callback);
@@ -200,7 +200,7 @@
@Test
public void unregisterCallback_verifyIsCalled() {
- final BluetoothVolumeControl.Callback callback = (device, volumeOffset) -> {};
+ final BluetoothVolumeControl.Callback callback = new BluetoothVolumeControl.Callback() {};
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
mProfile.unregisterCallback(callback);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java
new file mode 100644
index 0000000..d630301
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.media;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+/**
+ * Tests for {@link NoOpInfoMediaManager} to avoid exceptions in {@link InfoMediaManager}.
+ *
+ * <p>While {@link NoOpInfoMediaManager} should not perform any actions, it should still return
+ * placeholder information in certain cases to not change the behaviour of {@link InfoMediaManager}
+ * and prevent crashes.
+ */
+@RunWith(RobolectricTestRunner.class)
+public class NoOpInfoMediaManagerTest {
+ private InfoMediaManager mInfoMediaManager;
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = ApplicationProvider.getApplicationContext();
+ mInfoMediaManager =
+ new NoOpInfoMediaManager(
+ mContext,
+ /* packageName */ "FAKE_PACKAGE_NAME",
+ /* localBluetoothManager */ null);
+ }
+
+ @Test
+ public void getSessionVolumeMax_returnsNotFound() {
+ assertThat(mInfoMediaManager.getSessionVolumeMax()).isEqualTo(-1);
+ }
+
+ @Test
+ public void getSessionVolume_returnsNotFound() {
+ assertThat(mInfoMediaManager.getSessionVolume()).isEqualTo(-1);
+ }
+
+ @Test
+ public void getSessionName_returnsNull() {
+ assertThat(mInfoMediaManager.getSessionName()).isNull();
+ }
+
+ @Test
+ public void getRoutingSessionForPackage_returnsPlaceholderSession() {
+ // Make sure we return a placeholder routing session so that we avoid OOB exceptions.
+ assertThat(mInfoMediaManager.getRoutingSessionsForPackage()).hasSize(1);
+ }
+
+ @Test
+ public void getSelectedMediaDevices_returnsEmptyList() {
+ assertThat(mInfoMediaManager.getSelectedMediaDevices()).isEmpty();
+ }
+}
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index 94ea016..7ec3d24 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -67,6 +67,8 @@
"mockito-target-minus-junit4",
"platform-test-annotations",
"truth",
+ "Nene",
+ "Harrier",
],
libs: [
"android.test.base",
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index a490b6f..eaec617 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -268,6 +268,8 @@
Settings.Secure.EVEN_DIMMER_MIN_NITS,
Settings.Secure.STYLUS_POINTER_ICON_ENABLED,
Settings.Secure.CAMERA_EXTENSIONS_FALLBACK,
- Settings.Secure.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED
+ Settings.Secure.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED,
+ Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
+ Settings.Secure.AUDIO_DEVICE_INVENTORY
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 4cdf98cb..046d6e2 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -423,5 +423,7 @@
VALIDATORS.put(Secure.AUTOFILL_SERVICE, AUTOFILL_SERVICE_VALIDATOR);
VALIDATORS.put(Secure.STYLUS_POINTER_ICON_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.CAMERA_EXTENSIONS_FALLBACK, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.IMMERSIVE_MODE_CONFIRMATIONS, ANY_STRING_VALIDATOR);
+ VALIDATORS.put(Secure.AUDIO_DEVICE_INVENTORY, ANY_STRING_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 2a8eb9b..3266c12 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -707,23 +707,36 @@
// Update/add new keys
for (String key : keyValues.keySet()) {
String value = keyValues.get(key);
+
+ // Rename key if it's an aconfig flag.
+ String flagName = key;
+ if (Flags.stageAllAconfigFlags() && isConfigSettingsKey(mKey)) {
+ int slashIndex = flagName.indexOf("/");
+ boolean stageFlag = slashIndex > 0 && slashIndex != flagName.length();
+ boolean isAconfig = trunkFlagMap != null && trunkFlagMap.containsKey(flagName);
+ if (stageFlag && isAconfig) {
+ String flagWithoutNamespace = flagName.substring(slashIndex + 1);
+ flagName = "staged/" + namespace + "*" + flagWithoutNamespace;
+ }
+ }
+
String oldValue = null;
- Setting state = mSettings.get(key);
+ Setting state = mSettings.get(flagName);
if (state == null) {
- state = new Setting(key, value, false, packageName, null);
- mSettings.put(key, state);
- changedKeys.add(key); // key was added
+ state = new Setting(flagName, value, false, packageName, null);
+ mSettings.put(flagName, state);
+ changedKeys.add(flagName); // key was added
} else if (state.value != value) {
oldValue = state.value;
state.update(value, false, packageName, null, true,
/* overrideableByRestore */ false);
- changedKeys.add(key); // key was updated
+ changedKeys.add(flagName); // key was updated
} else {
// this key/value already exists, no change and no logging necessary
continue;
}
- FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, key, value, state.value,
+ FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, flagName, value, state.value,
oldValue, /* tag */ null, /* make default */ false,
getUserIdFromKey(mKey), FrameworkStatsLog.SETTING_CHANGED__REASON__UPDATED);
addHistoricalOperationLocked(HISTORICAL_OPERATION_UPDATE, state);
diff --git a/packages/SettingsProvider/test/AndroidTest.xml b/packages/SettingsProvider/test/AndroidTest.xml
index 0bf53cc..dccc2d3 100644
--- a/packages/SettingsProvider/test/AndroidTest.xml
+++ b/packages/SettingsProvider/test/AndroidTest.xml
@@ -14,6 +14,8 @@
limitations under the License.
-->
<configuration description="Run Settings Provider Tests.">
+ <option name="config-descriptor:metadata" key="parameter" value="multiuser" />
+
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
<option name="set-global-setting" key="verifier_verify_adb_installs" value="0" />
<option name="restore-settings" value="true" />
@@ -30,5 +32,7 @@
<option name="package" value="com.android.providers.setting.test" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
<option name="hidden-api-checks" value="false"/>
+ <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile" />
+ <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser" />
</test>
</configuration>
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 09d076e..6eb2dd0 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -670,7 +670,6 @@
Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED,
Settings.Secure.AUTOMATIC_STORAGE_MANAGER_LAST_RUN,
Settings.Secure.AUTOMATIC_STORAGE_MANAGER_TURNED_OFF_BY_POLICY,
- Settings.Secure.AUDIO_DEVICE_INVENTORY, // not controllable by user
Settings.Secure.AUDIO_SAFE_CSD_AS_A_FEATURE_ENABLED, // not controllable by user
Settings.Secure.BACKUP_AUTO_RESTORE,
Settings.Secure.BACKUP_ENABLED,
@@ -702,7 +701,6 @@
Settings.Secure.ENABLED_PRINT_SERVICES,
Settings.Secure.GLOBAL_ACTIONS_PANEL_AVAILABLE,
Settings.Secure.GLOBAL_ACTIONS_PANEL_DEBUG_ENABLED,
- Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
Settings.Secure.INCALL_BACK_BUTTON_BEHAVIOR,
Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY,
Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY,
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
new file mode 100644
index 0000000..ca1e4c1
--- /dev/null
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.settings;
+
+import static android.provider.Settings.Secure.ACCESSIBILITY_ENABLED;
+import static android.provider.Settings.Secure.SYNC_PARENT_SOUNDS;
+import static android.provider.Settings.System.RINGTONE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.pm.PackageManager;
+import android.support.test.uiautomator.UiDevice;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.bedstead.harrier.BedsteadJUnit4;
+import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.EnsureHasNoWorkProfile;
+import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
+import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
+import com.android.bedstead.harrier.annotations.RequireFeature;
+import com.android.bedstead.harrier.annotations.RequireRunOnInitialUser;
+import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.users.UserReference;
+import com.android.bedstead.nene.users.UserType;
+
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+@RunWith(BedsteadJUnit4.class)
+@RequireRunOnPrimaryUser
+public class SettingsProviderMultiUsersTest {
+
+ @ClassRule @Rule
+ public static final DeviceState sDeviceState = new DeviceState();
+
+ private static final String SETTINGS = "some_random_setting";
+
+ private static final String GET_SHELL_COMMAND = "settings get --user ";
+ private static final String SET_SHELL_COMMAND = "settings put --user ";
+ private static final String DELETE_SHELL_COMMAND = "settings delete --user ";
+
+ private static final String SPACE_GLOBAL = "global";
+ private static final String SPACE_SYSTEM = "system";
+ private static final String SPACE_SECURE = "secure";
+
+ private static final String CLONE_TO_MANAGED_PROFILE_SETTING = ACCESSIBILITY_ENABLED;
+ private static final String CLONE_FROM_PARENT_SETTINGS = RINGTONE;
+ private static final String SYNC_FROM_PARENT_SETTINGS = SYNC_PARENT_SOUNDS;
+
+ private UiDevice mUiDevice;
+ private UserReference mPrimaryUser;
+
+ @Before
+ public void setUp() throws Exception {
+ mPrimaryUser = sDeviceState.initialUser();
+
+ mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ }
+
+ @Test
+ @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
+ @EnsureHasWorkProfile
+ public void testSettings_workProfile() throws Exception {
+ UserReference profile = sDeviceState.workProfile();
+
+ // Settings.Global settings are shared between different users
+ assertSettingsShared(SPACE_GLOBAL, mPrimaryUser.id(), profile.id());
+ // Settings.System and Settings.Secure settings can be different on different users
+ assertSettingsDifferent(SPACE_SYSTEM, mPrimaryUser.id(), profile.id());
+ assertSettingsDifferent(SPACE_SECURE, mPrimaryUser.id(), profile.id());
+ }
+
+ @Test
+ @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
+ @RequireRunOnInitialUser
+ @EnsureHasSecondaryUser
+ public void testSettings_secondaryUser() throws Exception {
+ UserReference secondaryUser = sDeviceState.secondaryUser();
+
+ // Settings.Global settings are shared between different users
+ assertSettingsShared(SPACE_GLOBAL, mPrimaryUser.id(), secondaryUser.id());
+ // Settings.System and Settings.Secure settings can be different on different users
+ assertSettingsDifferent(SPACE_SYSTEM, mPrimaryUser.id(), secondaryUser.id());
+ assertSettingsDifferent(SPACE_SECURE, mPrimaryUser.id(), secondaryUser.id());
+ }
+
+ private void assertSettingsDifferent(String type, int userId1, int userId2) throws Exception {
+ // reset settings
+ setSetting(type, SETTINGS, "noValue", userId2);
+ waitForIdle();
+
+ // set the value with user 1
+ setSetting(type, SETTINGS, "value1", userId1);
+ waitForIdle();
+
+ // check no value with user 2
+ String value = getSetting(type, SETTINGS, userId2);
+ assertThat(value).startsWith("noValue");
+
+ // set the value with user 2
+ setSetting(type, SETTINGS, "value2", userId2);
+ waitForIdle();
+
+ // check the value with user 1 is not changed
+ value = getSetting(type, SETTINGS, userId1);
+ assertThat(value).startsWith("value1");
+ }
+
+ private void assertSettingsShared(String type, int userId1, int userId2) throws Exception {
+ // reset settings
+ setSetting(type, SETTINGS, "noValue", userId2);
+ waitForIdle();
+
+ // set the value with user 1
+ setSetting(type, SETTINGS, "value1", userId1);
+ waitForIdle();
+
+ // check no value with user 2
+ String value = getSetting(type, SETTINGS, userId2);
+ assertThat(value).startsWith("value1");
+
+ // set the value with user 2
+ setSetting(type, SETTINGS, "value2", userId2);
+ waitForIdle();
+
+ // check the value with user 1 is not changed
+ value = getSetting(type, SETTINGS, userId1);
+ assertThat(value).startsWith("value2");
+ }
+
+ @Test
+ @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
+ @EnsureHasNoWorkProfile
+ public void testSettings_profile_cloneToManagedProfile() throws Exception {
+ assertSettingsCloned(SPACE_SECURE, CLONE_TO_MANAGED_PROFILE_SETTING, false);
+ }
+
+ @Test
+ @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
+ @EnsureHasNoWorkProfile
+ public void testSettings_profile_cloneFromParent() throws Exception {
+ assertSettingsCloned(SPACE_SYSTEM, CLONE_FROM_PARENT_SETTINGS, true);
+ }
+
+ private void assertSettingsCloned(String type, String name, boolean isSyncSettings)
+ throws Exception {
+ boolean resetSyncValue = false;
+ if (isSyncSettings) {
+ // set to sync settings from parent
+ final String oldSyncValue =
+ getSetting(SPACE_SECURE, SYNC_FROM_PARENT_SETTINGS, mPrimaryUser.id());
+ resetSyncValue = oldSyncValue.startsWith("0");
+ if (resetSyncValue) {
+ setSetting(SPACE_SECURE, SYNC_FROM_PARENT_SETTINGS, "1", mPrimaryUser.id());
+ waitForIdle();
+ }
+ }
+
+ final String oldValue = getSetting(type, name, mPrimaryUser.id());
+
+ try (UserReference myProfile = TestApis.users().createUser()
+ .parent(mPrimaryUser)
+ .type(TestApis.users().supportedType(UserType.MANAGED_PROFILE_TYPE_NAME))
+ .createAndStart()) {
+ String value = getSetting(type, name, myProfile.id());
+ assertThat(value).isEqualTo(oldValue);
+
+ String newValue;
+ if (isSyncSettings) {
+ newValue = generateNewValue(oldValue);
+ } else {
+ newValue = oldValue.startsWith("0") ? "1" : "0";
+ }
+
+ setSetting(type, name, newValue, mPrimaryUser.id());
+ waitForIdle();
+
+ value = getSetting(type, name, myProfile.id());
+ assertThat(value).startsWith(newValue);
+ } finally {
+ // reset settings
+ setSetting(type, name, oldValue, mPrimaryUser.id());
+ if (resetSyncValue) {
+ setSetting(SPACE_SECURE, SYNC_FROM_PARENT_SETTINGS, "0", mPrimaryUser.id());
+ }
+ }
+ }
+
+ private String generateNewValue(String oldValue) {
+ String newValue = oldValue.replace("\n", "");
+ if (newValue.endsWith("0")) {
+ final int size = newValue.length();
+ newValue = newValue.substring(0, size - 1) + "1";
+ } else {
+ final int size = newValue.length();
+ newValue = newValue.substring(0, size - 1) + "0";
+ }
+ return newValue;
+ }
+
+ @Test
+ @RequireRunOnInitialUser
+ @EnsureHasSecondaryUser
+ public void testSettings_stopAndRestartSecondaryUser() throws Exception {
+ UserReference secondaryUser = sDeviceState.secondaryUser();
+
+ assertSettingsDifferent(SPACE_SECURE, mPrimaryUser.id(), secondaryUser.id());
+
+ secondaryUser.stop();
+
+ assertSettingsDifferent(SPACE_SECURE, mPrimaryUser.id(), secondaryUser.id());
+
+ secondaryUser.start();
+
+ assertSettingsDifferent(SPACE_SECURE, mPrimaryUser.id(), secondaryUser.id());
+ }
+
+ private void waitForIdle() {
+ final UiDevice uiDevice = UiDevice.getInstance(
+ InstrumentationRegistry.getInstrumentation());
+ uiDevice.waitForIdle();
+ }
+
+ private String getSetting(String type, String name, int userId) throws Exception {
+ return executeShellCmd(GET_SHELL_COMMAND + userId + " " + type + " " + name);
+ }
+
+ private void setSetting(String type, String name, String setting, int userId)
+ throws Exception {
+ setSetting(name, setting, userId + " " + type);
+ }
+
+ private void setSetting(String name, String setting, String type) throws Exception {
+ if (setting == null || setting.equals("null")) {
+ executeShellCmd(DELETE_SHELL_COMMAND + type + " " + name);
+ } else {
+ setting = setting.replace("\n", "");
+ executeShellCmd(SET_SHELL_COMMAND + type + " " + name + " " + setting);
+ }
+ }
+
+ private String executeShellCmd(String command) throws IOException {
+ return mUiDevice.executeShellCommand(command);
+ }
+}
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 33362a2..e0e31d7 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -740,6 +740,51 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_STAGE_ALL_ACONFIG_FLAGS)
+ public void testSetSettingsLockedStagesAconfigFlags() throws Exception {
+ int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
+
+ SettingsState settingsState = new SettingsState(
+ InstrumentationRegistry.getContext(), mLock, mSettingsFile, configKey,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+
+ String prefix = "test_namespace";
+ String packageName = "com.android.flags";
+ Map<String, String> keyValues =
+ Map.of("test_namespace/com.android.flags.flag3", "true");
+
+ parsed_flags flags = parsed_flags
+ .newBuilder()
+ .addParsedFlag(parsed_flag
+ .newBuilder()
+ .setPackage(packageName)
+ .setName("flag3")
+ .setNamespace(prefix)
+ .setDescription("test flag")
+ .addBug("12345678")
+ .setState(Aconfig.flag_state.DISABLED)
+ .setPermission(Aconfig.flag_permission.READ_WRITE))
+ .build();
+
+ synchronized (mLock) {
+ settingsState.loadAconfigDefaultValues(
+ flags.toByteArray(), settingsState.getAconfigDefaultValues());
+ List<String> updates =
+ settingsState.setSettingsLocked("test_namespace/", keyValues, packageName);
+ assertEquals(1, updates.size());
+ assertEquals(updates.get(0), "staged/test_namespace*com.android.flags.flag3");
+
+ SettingsState.Setting s;
+
+ s = settingsState.getSettingLocked("test_namespace/com.android.flags.flag3");
+ assertNull(s.getValue());
+
+ s = settingsState.getSettingLocked("staged/test_namespace*com.android.flags.flag3");
+ assertEquals("true", s.getValue());
+ }
+ }
+
+ @Test
public void testsetSettingsLockedKeepTrunkDefault() throws Exception {
final PrintStream os = new PrintStream(new FileOutputStream(mSettingsFile));
os.print(
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index eb2d13d..43ea3ec 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -698,6 +698,9 @@
<!-- Permission required for CTS test - CtsWearableSensingServiceTestCases -->
<uses-permission android:name="android.permission.MANAGE_WEARABLE_SENSING_SERVICE" />
+ <!-- Permission required for CTS test - OnDeviceIntelligenceManagerTest -->
+ <uses-permission android:name="android.permission.USE_ON_DEVICE_INTELLIGENCE" />
+
<!-- Permission required for CTS test - CallAudioInterceptionTest -->
<uses-permission android:name="android.permission.CALL_AUDIO_INTERCEPTION" />
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 04cb88d..617df74 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -155,6 +155,7 @@
"jsr330",
"lottie",
"LowLightDreamLib",
+ "TraceurCommon",
"motion_tool_lib",
"notification_flags_lib",
"PlatformComposeCore",
@@ -301,7 +302,9 @@
"androidx.compose.material_material-icons-extended",
"androidx.activity_activity-compose",
"androidx.compose.animation_animation-graphics",
+ "TraceurCommon",
],
+ skip_jarjar_repackage: true,
}
android_library {
@@ -352,6 +355,7 @@
test: true,
extra_check_modules: ["SystemUILintChecker"],
},
+ skip_jarjar_repackage: true,
}
android_app {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 12e8f57..e62c77dc 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -179,6 +179,7 @@
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT"/>
<uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
+ <uses-permission android:name="android.permission.RECORD_SENSITIVE_CONTENT"/>
<!-- Assist -->
<uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" />
@@ -273,6 +274,9 @@
<!-- to control accessibility volume -->
<uses-permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME" />
+ <!-- to change spatial audio -->
+ <uses-permission android:name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS" />
+
<!-- to access ResolverRankerServices -->
<uses-permission android:name="android.permission.BIND_RESOLVER_RANKER_SERVICE" />
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index d05d40d..1c98630 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -115,10 +115,10 @@
}
flag {
- name: "notifications_background_media_icons"
+ name: "notifications_background_icons"
namespace: "systemui"
- description: "Updates icons for media notifications in the background."
- bug: "315143160"
+ description: "Moves part of the notification icon updates to the background."
+ bug: "315143361"
metadata {
purpose: PURPOSE_BUGFIX
}
@@ -463,6 +463,13 @@
}
flag {
+ name: "enable_contextual_tips_frequency_cap"
+ description: "Enables frequency capping for contextual tips, e.g. 1x/day, 2x/week, 3x/lifetime."
+ namespace: "systemui"
+ bug: "322891421"
+}
+
+flag {
name: "enable_contextual_tips"
description: "Enables showing contextual tips."
namespace: "systemui"
@@ -610,3 +617,21 @@
description: "Refactors media code to follow the recommended architecture"
bug: "326408371"
}
+
+flag {
+ name: "qs_tile_focus_state"
+ namespace: "systemui"
+ description: "enables new focus outline for qs tiles when focused on with physical keyboard"
+ bug: "312899524"
+}
+
+flag {
+ name: "edgeback_gesture_handler_get_running_tasks_background"
+ namespace: "systemui"
+ description: "Decide whether to get the running tasks from activity manager in EdgebackGestureHandler"
+ " class on the background thread."
+ bug: "325041960"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
index 5d5f12e..3f57f88 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
@@ -337,6 +337,7 @@
if (ghostedView is LaunchableView) {
// Restore the ghosted view visibility.
ghostedView.setShouldBlockVisibilityChanges(false)
+ ghostedView.onActivityLaunchAnimationEnd()
} else {
// Make the ghosted view visible. We ensure that the view is considered VISIBLE by
// accessibility by first making it INVISIBLE then VISIBLE (see b/204944038#comment17
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
index ed8e705..da6ccaa 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
@@ -38,6 +38,9 @@
* @param block whether we should block/postpone all calls to `setVisibility`.
*/
fun setShouldBlockVisibilityChanges(block: Boolean)
+
+ /** Perform an action when the activity launch animation ends */
+ fun onActivityLaunchAnimationEnd() {}
}
/** A delegate that can be used by views to make the implementation of [LaunchableView] easier. */
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt b/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
index ef15c84..9a99649 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
@@ -21,22 +21,25 @@
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.DragInteraction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
-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.height
-import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderState
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -44,17 +47,28 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.RoundRect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.clipPath
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasurePolicy
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
-import com.android.compose.modifiers.padding
+import androidx.compose.ui.util.fastFirst
+import androidx.compose.ui.util.fastFirstOrNull
import com.android.compose.theme.LocalAndroidColorScheme
/**
@@ -62,15 +76,16 @@
*
* @param onValueChangeFinished is called when the slider settles on a [value]. This callback
* shouldn't be used to react to value changes. Use [onValueChange] instead
- * @param interactionSource - the [MutableInteractionSource] representing the stream of Interactions
+ * @param interactionSource the [MutableInteractionSource] representing the stream of Interactions
* for this slider. You can create and pass in your own remembered instance to observe
* Interactions and customize the appearance / behavior of this slider in different states.
- * @param colors - slider color scheme.
- * @param draggingCornersRadius - radius of the slider indicator when the user drags it
- * @param icon - icon at the start of the slider. Icon is limited to a square space at the start of
- * the slider
- * @param label - control shown next to the icon.
+ * @param colors determine slider color scheme.
+ * @param draggingCornersRadius is the radius of the slider indicator when the user drags it
+ * @param icon at the start of the slider. Icon is limited to a square space at the start of the
+ * slider
+ * @param label is shown next to the icon.
*/
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PlatformSlider(
value: Float,
@@ -86,7 +101,7 @@
label: (@Composable (isDragging: Boolean) -> Unit)? = null,
) {
val sliderHeight: Dp = 64.dp
- val iconWidth: Dp = sliderHeight
+ val thumbSize: Dp = sliderHeight
var isDragging by remember { mutableStateOf(false) }
LaunchedEffect(interactionSource) {
interactionSource.interactions.collect { interaction ->
@@ -101,16 +116,6 @@
}
}
}
- val paddingStart by
- animateDpAsState(
- targetValue =
- if ((!isDragging && value == valueRange.start) || icon == null) {
- 16.dp
- } else {
- 0.dp
- },
- label = "LabelIconSpacingAnimation"
- )
Box(modifier = modifier.height(sliderHeight)) {
Slider(
@@ -126,130 +131,275 @@
sliderState = it,
enabled = enabled,
colors = colors,
- iconWidth = iconWidth,
draggingCornersRadius = draggingCornersRadius,
sliderHeight = sliderHeight,
+ thumbSize = thumbSize,
isDragging = isDragging,
- modifier = Modifier,
+ label = label,
+ icon = icon,
+ modifier = Modifier.fillMaxSize(),
)
},
- thumb = { Spacer(Modifier.width(iconWidth).height(sliderHeight)) },
+ thumb = { Spacer(Modifier.size(thumbSize)) },
)
- if (icon != null || label != null) {
- Row(modifier = Modifier.fillMaxSize()) {
- icon?.let { iconComposable ->
- Box(
- modifier = Modifier.fillMaxHeight().aspectRatio(1f),
- contentAlignment = Alignment.Center,
- ) {
- iconComposable(isDragging)
- }
- }
-
- label?.let { labelComposable ->
- Box(
- modifier =
- Modifier.fillMaxHeight()
- .weight(1f)
- .padding(
- start = { paddingStart.roundToPx() },
- end = { sliderHeight.roundToPx() / 2 },
- ),
- contentAlignment = Alignment.CenterStart,
- ) {
- labelComposable(isDragging)
- }
- }
- }
- }
+ Spacer(
+ Modifier.padding(8.dp)
+ .size(4.dp)
+ .align(Alignment.CenterEnd)
+ .background(color = colors.indicatorColor, shape = CircleShape)
+ )
}
}
+private enum class TrackComponent(val zIndex: Float) {
+ Background(0f),
+ Icon(1f),
+ Label(1f),
+}
+
@Composable
private fun Track(
sliderState: SliderState,
enabled: Boolean,
colors: PlatformSliderColors,
- iconWidth: Dp,
draggingCornersRadius: Dp,
sliderHeight: Dp,
+ thumbSize: Dp,
isDragging: Boolean,
+ icon: (@Composable (isDragging: Boolean) -> Unit)?,
+ label: (@Composable (isDragging: Boolean) -> Unit)?,
modifier: Modifier = Modifier,
) {
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
- val iconWidthPx: Float
- val halfIconWidthPx: Float
- val targetIndicatorRadiusPx: Float
- val halfSliderHeightPx: Float
- with(LocalDensity.current) {
- halfSliderHeightPx = sliderHeight.toPx() / 2
- iconWidthPx = iconWidth.toPx()
- halfIconWidthPx = iconWidthPx / 2
- targetIndicatorRadiusPx =
- if (isDragging) draggingCornersRadius.toPx() else halfSliderHeightPx
- }
+ var drawingState: DrawingState by remember { mutableStateOf(DrawingState()) }
+ Layout(
+ modifier = modifier,
+ content = {
+ TrackBackground(
+ modifier = Modifier.layoutId(TrackComponent.Background),
+ drawingState = drawingState,
+ enabled = enabled,
+ colors = colors,
+ draggingCornersRadiusActive = draggingCornersRadius,
+ draggingCornersRadiusIdle = sliderHeight / 2,
+ isDragging = isDragging,
+ )
+ if (icon != null) {
+ Box(
+ modifier = Modifier.layoutId(TrackComponent.Icon).clip(CircleShape),
+ contentAlignment = Alignment.Center,
+ ) {
+ CompositionLocalProvider(
+ LocalContentColor provides
+ if (enabled) colors.iconColor else colors.disabledIconColor
+ ) {
+ icon(isDragging)
+ }
+ }
+ }
+ if (label != null) {
+ val offsetX by
+ animateFloatAsState(
+ targetValue =
+ if (enabled) {
+ if (drawingState.isLabelOnTopOfIndicator) {
+ drawingState.iconWidth.coerceAtLeast(
+ LocalDensity.current.run { 16.dp.toPx() }
+ )
+ } else {
+ val indicatorWidth =
+ drawingState.indicatorRight - drawingState.indicatorLeft
+ indicatorWidth + LocalDensity.current.run { 16.dp.toPx() }
+ }
+ } else {
+ drawingState.iconWidth
+ },
+ label = "LabelIconSpacingAnimation"
+ )
+ Box(
+ modifier =
+ Modifier.layoutId(TrackComponent.Label).offset {
+ IntOffset(offsetX.toInt(), 0)
+ },
+ contentAlignment = Alignment.CenterStart,
+ ) {
+ CompositionLocalProvider(
+ LocalContentColor provides
+ colors.getLabelColor(
+ isEnabled = enabled,
+ isLabelOnTopOfTheIndicator = drawingState.isLabelOnTopOfIndicator,
+ )
+ ) {
+ label(isDragging)
+ }
+ }
+ }
+ },
+ measurePolicy =
+ TrackMeasurePolicy(
+ sliderState = sliderState,
+ thumbSize = LocalDensity.current.run { thumbSize.roundToPx() },
+ isRtl = isRtl,
+ onDrawingStateMeasured = { drawingState = it }
+ )
+ )
+}
- val indicatorRadiusPx: Float by
- animateFloatAsState(
- targetValue = targetIndicatorRadiusPx,
+@Composable
+private fun TrackBackground(
+ drawingState: DrawingState,
+ enabled: Boolean,
+ colors: PlatformSliderColors,
+ draggingCornersRadiusActive: Dp,
+ draggingCornersRadiusIdle: Dp,
+ isDragging: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ val indicatorRadiusDp: Dp by
+ animateDpAsState(
+ targetValue =
+ if (isDragging) draggingCornersRadiusActive else draggingCornersRadiusIdle,
label = "PlatformSliderCornersAnimation",
)
val trackColor = colors.getTrackColor(enabled)
val indicatorColor = colors.getIndicatorColor(enabled)
- val trackCornerRadius = CornerRadius(halfSliderHeightPx, halfSliderHeightPx)
- val indicatorCornerRadius = CornerRadius(indicatorRadiusPx, indicatorRadiusPx)
Canvas(modifier.fillMaxSize()) {
+ val trackCornerRadius = CornerRadius(size.height / 2, size.height / 2)
val trackPath = Path()
trackPath.addRoundRect(
RoundRect(
- left = -halfIconWidthPx,
+ left = 0f,
top = 0f,
- right = size.width + halfIconWidthPx,
- bottom = size.height,
+ right = drawingState.totalWidth,
+ bottom = drawingState.totalHeight,
cornerRadius = trackCornerRadius,
)
)
drawPath(path = trackPath, color = trackColor)
+ val indicatorCornerRadius = CornerRadius(indicatorRadiusDp.toPx(), indicatorRadiusDp.toPx())
clipPath(trackPath) {
val indicatorPath = Path()
- if (isRtl) {
- indicatorPath.addRoundRect(
- RoundRect(
- left =
- size.width -
- size.width * sliderState.coercedNormalizedValue -
- halfIconWidthPx,
- top = 0f,
- right = size.width + iconWidthPx,
- bottom = size.height,
- topLeftCornerRadius = indicatorCornerRadius,
- topRightCornerRadius = trackCornerRadius,
- bottomRightCornerRadius = trackCornerRadius,
- bottomLeftCornerRadius = indicatorCornerRadius,
- )
+ indicatorPath.addRoundRect(
+ RoundRect(
+ left = drawingState.indicatorLeft,
+ top = drawingState.indicatorTop,
+ right = drawingState.indicatorRight,
+ bottom = drawingState.indicatorBottom,
+ topLeftCornerRadius = trackCornerRadius,
+ topRightCornerRadius = indicatorCornerRadius,
+ bottomRightCornerRadius = indicatorCornerRadius,
+ bottomLeftCornerRadius = trackCornerRadius,
)
- } else {
- indicatorPath.addRoundRect(
- RoundRect(
- left = -halfIconWidthPx,
- top = 0f,
- right = size.width * sliderState.coercedNormalizedValue + halfIconWidthPx,
- bottom = size.height,
- topLeftCornerRadius = trackCornerRadius,
- topRightCornerRadius = indicatorCornerRadius,
- bottomRightCornerRadius = indicatorCornerRadius,
- bottomLeftCornerRadius = trackCornerRadius,
- )
- )
- }
+ )
drawPath(path = indicatorPath, color = indicatorColor)
}
}
}
+/** Measures track components sizes and calls [onDrawingStateMeasured] when it's done. */
+private class TrackMeasurePolicy(
+ private val sliderState: SliderState,
+ private val thumbSize: Int,
+ private val isRtl: Boolean,
+ private val onDrawingStateMeasured: (DrawingState) -> Unit,
+) : MeasurePolicy {
+
+ override fun MeasureScope.measure(
+ measurables: List<Measurable>,
+ constraints: Constraints
+ ): MeasureResult {
+ // Slider adds a paddings to the Track to make spase for thumb
+ val desiredWidth = constraints.maxWidth + thumbSize
+ val desiredHeight = constraints.maxHeight
+ val backgroundPlaceable: Placeable =
+ measurables
+ .fastFirst { it.layoutId == TrackComponent.Background }
+ .measure(Constraints(desiredWidth, desiredWidth, desiredHeight, desiredHeight))
+
+ val iconPlaceable: Placeable? =
+ measurables
+ .fastFirstOrNull { it.layoutId == TrackComponent.Icon }
+ ?.measure(
+ Constraints(
+ minWidth = desiredHeight,
+ maxWidth = desiredHeight,
+ minHeight = desiredHeight,
+ maxHeight = desiredHeight,
+ )
+ )
+
+ val iconSize = iconPlaceable?.width ?: 0
+ val labelMaxWidth = (desiredWidth - iconSize) / 2
+ val labelPlaceable: Placeable? =
+ measurables
+ .fastFirstOrNull { it.layoutId == TrackComponent.Label }
+ ?.measure(
+ Constraints(
+ minWidth = 0,
+ maxWidth = labelMaxWidth,
+ minHeight = desiredHeight,
+ maxHeight = desiredHeight,
+ )
+ )
+
+ val drawingState =
+ if (isRtl) {
+ DrawingState(
+ isRtl = true,
+ totalWidth = desiredWidth.toFloat(),
+ totalHeight = desiredHeight.toFloat(),
+ indicatorLeft =
+ (desiredWidth - iconSize) * (1 - sliderState.coercedNormalizedValue),
+ indicatorTop = 0f,
+ indicatorRight = desiredWidth.toFloat(),
+ indicatorBottom = desiredHeight.toFloat(),
+ iconWidth = iconSize.toFloat(),
+ labelWidth = labelPlaceable?.width?.toFloat() ?: 0f,
+ )
+ } else {
+ DrawingState(
+ isRtl = false,
+ totalWidth = desiredWidth.toFloat(),
+ totalHeight = desiredHeight.toFloat(),
+ indicatorLeft = 0f,
+ indicatorTop = 0f,
+ indicatorRight =
+ iconSize + (desiredWidth - iconSize) * sliderState.coercedNormalizedValue,
+ indicatorBottom = desiredHeight.toFloat(),
+ iconWidth = iconSize.toFloat(),
+ labelWidth = labelPlaceable?.width?.toFloat() ?: 0f,
+ )
+ }
+
+ onDrawingStateMeasured(drawingState)
+
+ return layout(desiredWidth, desiredHeight) {
+ backgroundPlaceable.placeRelative(0, 0, TrackComponent.Background.zIndex)
+
+ iconPlaceable?.placeRelative(0, 0, TrackComponent.Icon.zIndex)
+ labelPlaceable?.placeRelative(0, 0, TrackComponent.Label.zIndex)
+ }
+ }
+}
+
+private data class DrawingState(
+ val isRtl: Boolean = false,
+ val totalWidth: Float = 0f,
+ val totalHeight: Float = 0f,
+ val indicatorLeft: Float = 0f,
+ val indicatorTop: Float = 0f,
+ val indicatorRight: Float = 0f,
+ val indicatorBottom: Float = 0f,
+ val iconWidth: Float = 0f,
+ val labelWidth: Float = 0f,
+)
+
+private val DrawingState.isLabelOnTopOfIndicator: Boolean
+ get() = labelWidth < indicatorRight - indicatorLeft - iconWidth
+
/** [SliderState.value] normalized using [SliderState.valueRange]. The result belongs to [0, 1] */
private val SliderState.coercedNormalizedValue: Float
get() {
@@ -268,17 +418,19 @@
* @param trackColor fills the track of the slider. This is a "background" of the slider
* @param indicatorColor fills the slider from the start to the value
* @param iconColor is the default icon color
- * @param labelColor is the default icon color
+ * @param labelColorOnIndicator is the label color for when it's shown on top of the indicator
+ * @param labelColorOnTrack is the label color for when it's shown on top of the track
* @param disabledTrackColor is the [trackColor] when the PlatformSlider#enabled == false
* @param disabledIndicatorColor is the [indicatorColor] when the PlatformSlider#enabled == false
* @param disabledIconColor is the [iconColor] when the PlatformSlider#enabled == false
- * @param disabledLabelColor is the [labelColor] when the PlatformSlider#enabled == false
+ * @param disabledLabelColor is the label color when the PlatformSlider#enabled == false
*/
data class PlatformSliderColors(
val trackColor: Color,
val indicatorColor: Color,
val iconColor: Color,
- val labelColor: Color,
+ val labelColorOnIndicator: Color,
+ val labelColorOnTrack: Color,
val disabledTrackColor: Color,
val disabledIndicatorColor: Color,
val disabledIconColor: Color,
@@ -300,10 +452,11 @@
@Composable
private fun lightThemePlatformSliderColors() =
PlatformSliderColors(
- trackColor = MaterialTheme.colorScheme.tertiaryContainer,
- indicatorColor = LocalAndroidColorScheme.current.tertiaryFixedDim,
- iconColor = MaterialTheme.colorScheme.onTertiaryContainer,
- labelColor = MaterialTheme.colorScheme.onTertiaryContainer,
+ trackColor = LocalAndroidColorScheme.current.tertiaryFixedDim,
+ indicatorColor = MaterialTheme.colorScheme.tertiary,
+ iconColor = MaterialTheme.colorScheme.onTertiary,
+ labelColorOnIndicator = MaterialTheme.colorScheme.onTertiary,
+ labelColorOnTrack = LocalAndroidColorScheme.current.onTertiaryFixed,
disabledTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
disabledIndicatorColor = MaterialTheme.colorScheme.surfaceContainerHighest,
disabledIconColor = MaterialTheme.colorScheme.outline,
@@ -314,10 +467,11 @@
@Composable
private fun darkThemePlatformSliderColors() =
PlatformSliderColors(
- trackColor = MaterialTheme.colorScheme.onTertiary,
- indicatorColor = LocalAndroidColorScheme.current.onTertiaryFixedVariant,
+ trackColor = MaterialTheme.colorScheme.tertiary,
+ indicatorColor = MaterialTheme.colorScheme.tertiary,
iconColor = MaterialTheme.colorScheme.onTertiaryContainer,
- labelColor = MaterialTheme.colorScheme.onTertiaryContainer,
+ labelColorOnIndicator = MaterialTheme.colorScheme.onTertiary,
+ labelColorOnTrack = LocalAndroidColorScheme.current.onTertiaryFixed,
disabledTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
disabledIndicatorColor = MaterialTheme.colorScheme.surfaceContainerHighest,
disabledIconColor = MaterialTheme.colorScheme.outline,
@@ -329,3 +483,14 @@
private fun PlatformSliderColors.getIndicatorColor(isEnabled: Boolean): Color =
if (isEnabled) indicatorColor else disabledIndicatorColor
+
+private fun PlatformSliderColors.getLabelColor(
+ isEnabled: Boolean,
+ isLabelOnTopOfTheIndicator: Boolean
+): Color {
+ return if (isEnabled) {
+ if (isLabelOnTopOfTheIndicator) labelColorOnIndicator else labelColorOnTrack
+ } else {
+ disabledLabelColor
+ }
+}
diff --git a/core/java/android/app/ondeviceintelligence/Content.aidl b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt
similarity index 68%
copy from core/java/android/app/ondeviceintelligence/Content.aidl
copy to packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt
index 40f0ef9..a066b38 100644
--- a/core/java/android/app/ondeviceintelligence/Content.aidl
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2024, The Android Open Source Project
+/*
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package android.app.ondeviceintelligence;
+package com.android.systemui.volume.panel.component.spatialaudio
-/**
- * @hide
- */
-parcelable Content;
+import dagger.Module
+
+@Module interface SpatialAudioModule
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 9ee69bc..d0c4984 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -24,10 +24,10 @@
import com.android.compose.animation.scene.updateSceneTransitionLayoutState
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.communal.ui.compose.extensions.allowGestures
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
object Communal {
object Elements {
@@ -63,6 +63,7 @@
fun CommunalContainer(
modifier: Modifier = Modifier,
viewModel: CommunalViewModel,
+ dialogFactory: SystemUIDialogFactory,
) {
val currentScene: SceneKey by viewModel.currentScene.collectAsState(CommunalScenes.Blank)
val sceneTransitionLayoutState =
@@ -82,7 +83,7 @@
SceneTransitionLayout(
state = sceneTransitionLayoutState,
- modifier = modifier.fillMaxSize().allowGestures(allowed = touchesAllowed),
+ modifier = modifier.fillMaxSize(),
swipeSourceDetector =
FixedSizeEdgeDetector(
dimensionResource(id = R.dimen.communal_gesture_initiation_width)
@@ -91,9 +92,14 @@
scene(
CommunalScenes.Blank,
userActions =
- mapOf(
- Swipe(SwipeDirection.Left, fromSource = Edge.Right) to CommunalScenes.Communal
- )
+ if (touchesAllowed) {
+ mapOf(
+ Swipe(SwipeDirection.Left, fromSource = Edge.Right) to
+ CommunalScenes.Communal
+ )
+ } else {
+ emptyMap()
+ }
) {
// This scene shows nothing only allowing for transitions to the communal scene.
Box(modifier = Modifier.fillMaxSize())
@@ -102,9 +108,15 @@
scene(
CommunalScenes.Communal,
userActions =
- mapOf(Swipe(SwipeDirection.Right, fromSource = Edge.Left) to CommunalScenes.Blank),
+ if (touchesAllowed) {
+ mapOf(
+ Swipe(SwipeDirection.Right, fromSource = Edge.Left) to CommunalScenes.Blank
+ )
+ } else {
+ emptyMap()
+ },
) {
- CommunalScene(viewModel, modifier = modifier)
+ CommunalScene(viewModel, dialogFactory, modifier = modifier)
}
}
}
@@ -113,6 +125,7 @@
@Composable
private fun SceneScope.CommunalScene(
viewModel: BaseCommunalViewModel,
+ dialogFactory: SystemUIDialogFactory,
modifier: Modifier = Modifier,
) {
Box(
@@ -121,5 +134,7 @@
.fillMaxSize()
.background(LocalAndroidColorScheme.current.outlineVariant),
)
- Box(modifier.element(Communal.Elements.Content)) { CommunalHub(viewModel = viewModel) }
+ Box(modifier.element(Communal.Elements.Content)) {
+ CommunalHub(viewModel = viewModel, dialogFactory = dialogFactory)
+ }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 078da1c86..6a510bd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -29,6 +29,7 @@
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
@@ -66,6 +67,7 @@
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
@@ -74,6 +76,7 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
@@ -82,17 +85,19 @@
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.key.onPreviewKeyEvent
+import androidx.compose.ui.input.pointer.motionEventSpy
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInWindow
-import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
@@ -105,6 +110,7 @@
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Popup
import androidx.core.view.setPadding
+import androidx.window.layout.WindowMetricsCalculator
import com.android.compose.modifiers.thenIf
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
@@ -115,11 +121,13 @@
import com.android.systemui.communal.ui.compose.extensions.allowGestures
import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture
import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
-import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming
+import com.android.systemui.communal.ui.compose.extensions.observeTaps
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import kotlinx.coroutines.launch
@OptIn(ExperimentalComposeUiApi::class)
@@ -127,6 +135,7 @@
fun CommunalHub(
modifier: Modifier = Modifier,
viewModel: BaseCommunalViewModel,
+ dialogFactory: SystemUIDialogFactory? = null,
widgetConfigurator: WidgetConfigurator? = null,
onOpenWidgetPicker: (() -> Unit)? = null,
onEditDone: (() -> Unit)? = null,
@@ -150,6 +159,8 @@
val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize)
val contentOffset = beforeContentPadding(contentPadding).toOffset()
+ ScrollOnNewSmartspaceEffect(viewModel, gridState)
+
Box(
modifier =
modifier
@@ -159,7 +170,7 @@
.pointerInput(gridState, contentOffset, contentListState) {
// If not in edit mode, don't allow selecting items.
if (!viewModel.isEditMode) return@pointerInput
- observeTapsWithoutConsuming { offset ->
+ observeTaps { offset ->
val adjustedOffset = offset - contentOffset
val index = firstIndexAtOffset(gridState, adjustedOffset)
val key = index?.let { keyAtIndexIfEditable(contentListState.list, index) }
@@ -168,33 +179,43 @@
}
.thenIf(!viewModel.isEditMode) {
Modifier.pointerInput(
- gridState,
- contentOffset,
- communalContent,
- gridCoordinates
- ) {
- detectLongPressGesture { offset ->
- // Deduct both grid offset relative to its container and content offset.
- val adjustedOffset =
- gridCoordinates?.let {
- offset - it.positionInWindow() - contentOffset
+ gridState,
+ contentOffset,
+ communalContent,
+ gridCoordinates
+ ) {
+ detectLongPressGesture { offset ->
+ // Deduct both grid offset relative to its container and content
+ // offset.
+ val adjustedOffset =
+ gridCoordinates?.let {
+ offset - it.positionInWindow() - contentOffset
+ }
+ val index =
+ adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
+ // Display the button only when the gesture initiates from widgets,
+ // the CTA tile, or an empty area on the screen. UMO/smartspace have
+ // their own long-press handlers. To prevent user confusion, we
+ // should
+ // not display this button.
+ if (
+ index == null ||
+ communalContent[index].isWidgetContent() ||
+ communalContent[index] is
+ CommunalContentModel.CtaTileInViewMode
+ ) {
+ isButtonToEditWidgetsShowing = true
}
- val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
- // Display the button only when the gesture initiates from widgets,
- // the CTA tile, or an empty area on the screen. UMO/smartspace have
- // their own long-press handlers. To prevent user confusion, we should
- // not display this button.
- if (
- index == null ||
- communalContent[index].isWidgetContent() ||
- communalContent[index] is CommunalContentModel.CtaTileInViewMode
- ) {
- isButtonToEditWidgetsShowing = true
+ val key =
+ index?.let { keyAtIndexIfEditable(communalContent, index) }
+ viewModel.setSelectedKey(key)
}
- val key = index?.let { keyAtIndexIfEditable(communalContent, index) }
- viewModel.setSelectedKey(key)
}
- }
+ .onPreviewKeyEvent {
+ onKeyEvent(viewModel)
+ false
+ }
+ .motionEventSpy { onMotionEvent(viewModel) }
},
) {
CommunalHubLazyGrid(
@@ -218,6 +239,17 @@
widgetConfigurator = widgetConfigurator,
)
+ // TODO(b/326060686): Remove this once keyguard indication area can persist over hub
+ if (viewModel is CommunalViewModel) {
+ val isUnlocked by viewModel.deviceUnlocked.collectAsState(initial = false)
+ LockStateIcon(
+ modifier =
+ Modifier.align(Alignment.BottomCenter)
+ .padding(bottom = Dimensions.LockIconBottomPadding),
+ isUnlocked = isUnlocked,
+ )
+ }
+
if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) {
Toolbar(
isDraggingToRemove = isDraggingToRemove,
@@ -254,6 +286,31 @@
)
}
+ if (viewModel is CommunalViewModel && dialogFactory != null) {
+ val isEnableWidgetDialogShowing by
+ viewModel.isEnableWidgetDialogShowing.collectAsState(false)
+ val isEnableWorkProfileDialogShowing by
+ viewModel.isEnableWorkProfileDialogShowing.collectAsState(false)
+
+ EnableWidgetDialog(
+ isEnableWidgetDialogVisible = isEnableWidgetDialogShowing,
+ dialogFactory = dialogFactory,
+ title = stringResource(id = R.string.dialog_title_to_allow_any_widget),
+ positiveButtonText = stringResource(id = R.string.button_text_to_open_settings),
+ onConfirm = viewModel::onEnableWidgetDialogConfirm,
+ onCancel = viewModel::onEnableWidgetDialogCancel
+ )
+
+ EnableWidgetDialog(
+ isEnableWidgetDialogVisible = isEnableWorkProfileDialogShowing,
+ dialogFactory = dialogFactory,
+ title = stringResource(id = R.string.work_mode_off_title),
+ positiveButtonText = stringResource(id = R.string.work_mode_turn_on),
+ onConfirm = viewModel::onEnableWorkProfileDialogConfirm,
+ onCancel = viewModel::onEnableWorkProfileDialogCancel
+ )
+ }
+
// This spacer covers the edge of the LazyHorizontalGrid and prevents it from receiving
// touches, so that the SceneTransitionLayout can intercept the touches and allow an edge
// swipe back to the blank scene.
@@ -266,6 +323,42 @@
}
}
+private fun onKeyEvent(viewModel: BaseCommunalViewModel) {
+ viewModel.signalUserInteraction()
+}
+
+private fun onMotionEvent(viewModel: BaseCommunalViewModel) {
+ viewModel.signalUserInteraction()
+}
+
+@Composable
+private fun ScrollOnNewSmartspaceEffect(
+ viewModel: BaseCommunalViewModel,
+ gridState: LazyGridState
+) {
+ val communalContent by viewModel.communalContent.collectAsState(initial = emptyList())
+ var smartspaceCount by remember { mutableStateOf(0) }
+
+ LaunchedEffect(communalContent) {
+ snapshotFlow { gridState.firstVisibleItemIndex }
+ .collect { index ->
+ val existingSmartspaceCount = smartspaceCount
+ smartspaceCount = communalContent.count { it.isSmartspace() }
+ val firstIndex = communalContent.indexOfFirst { it.isSmartspace() }
+
+ // Scroll to the beginning of the smartspace area whenever the number of
+ // smartspace elements grows
+ if (
+ existingSmartspaceCount < smartspaceCount &&
+ !viewModel.isEditMode &&
+ index > firstIndex
+ ) {
+ gridState.animateScrollToItem(firstIndex)
+ }
+ }
+ }
+}
+
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun BoxScope.CommunalHubLazyGrid(
@@ -353,17 +446,37 @@
}
} else {
CommunalContent(
+ modifier = cardModifier.animateItemPlacement(),
model = list[index],
viewModel = viewModel,
size = size,
selected = false,
- modifier = cardModifier,
)
}
}
}
}
+@Composable
+private fun LockStateIcon(
+ isUnlocked: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ val colors = LocalAndroidColorScheme.current
+ val resource =
+ if (isUnlocked) {
+ R.drawable.ic_unlocked
+ } else {
+ R.drawable.ic_lock
+ }
+ Icon(
+ painter = painterResource(id = resource),
+ contentDescription = null,
+ tint = colors.onPrimaryContainer,
+ modifier = modifier.size(Dimensions.LockIconSize),
+ )
+}
+
/**
* Toolbar that contains action buttons to
* 1) open the widget picker
@@ -551,7 +664,7 @@
WidgetContent(viewModel, model, size, selected, widgetConfigurator, modifier)
is CommunalContentModel.WidgetPlaceholder -> HighlightedItem(modifier)
is CommunalContentModel.WidgetContent.DisabledWidget ->
- DisabledWidgetPlaceholder(model, modifier)
+ DisabledWidgetPlaceholder(model, viewModel, modifier)
is CommunalContentModel.CtaTileInViewMode -> CtaTileInViewModeContent(viewModel, modifier)
is CommunalContentModel.CtaTileInEditMode ->
CtaTileInEditModeContent(modifier, onOpenWidgetPicker)
@@ -580,7 +693,7 @@
) {
val colors = LocalAndroidColorScheme.current
Card(
- modifier = modifier.padding(CardOutlineWidth),
+ modifier = modifier,
colors =
CardDefaults.cardColors(
containerColor = colors.primary,
@@ -651,7 +764,7 @@
}
val colors = LocalAndroidColorScheme.current
Card(
- modifier = modifier.padding(CardOutlineWidth),
+ modifier = modifier,
colors = CardDefaults.cardColors(containerColor = Color.Transparent),
border = BorderStroke(1.dp, colors.primary),
shape = RoundedCornerShape(200.dp),
@@ -689,23 +802,28 @@
modifier: Modifier = Modifier,
) {
Box(
- modifier = modifier,
+ modifier =
+ modifier.thenIf(!viewModel.isEditMode && model.inQuietMode) {
+ Modifier.pointerInput(Unit) {
+ // consume tap to prevent the child view from triggering interactions with the
+ // app widget
+ observeTaps(shouldConsume = true) { _ ->
+ viewModel.onOpenEnableWorkProfileDialog()
+ }
+ }
+ }
) {
- val paddingInPx =
- if (selected) with(LocalDensity.current) { CardOutlineWidth.toPx().toInt() } else 0
AndroidView(
modifier = Modifier.fillMaxSize().allowGestures(allowed = !viewModel.isEditMode),
factory = { context ->
model.appWidgetHost
.createViewForCommunal(context, model.appWidgetId, model.providerInfo)
- .apply { updateAppWidgetSize(Bundle.EMPTY, listOf(size)) }
- },
- update = { view ->
- // Remove the extra padding applied to AppWidgetHostView to allow widgets to
- // occupy the entire box. The added padding is now adjusted to leave only
- // sufficient space for displaying the outline around the box when the widget
- // is selected.
- view.setPadding(paddingInPx)
+ .apply {
+ updateAppWidgetSize(Bundle.EMPTY, listOf(size))
+ // Remove the extra padding applied to AppWidgetHostView to allow widgets to
+ // occupy the entire box.
+ setPadding(0)
+ }
},
// For reusing composition in lazy lists.
onReset = {},
@@ -765,6 +883,7 @@
@Composable
fun DisabledWidgetPlaceholder(
model: CommunalContentModel.WidgetContent.DisabledWidget,
+ viewModel: BaseCommunalViewModel,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
@@ -778,10 +897,17 @@
Column(
modifier =
- modifier.background(
- MaterialTheme.colorScheme.surfaceVariant,
- RoundedCornerShape(dimensionResource(system_app_widget_background_radius))
- ),
+ modifier
+ .background(
+ MaterialTheme.colorScheme.surfaceVariant,
+ RoundedCornerShape(dimensionResource(system_app_widget_background_radius))
+ )
+ .clickable(
+ enabled = !viewModel.isEditMode,
+ interactionSource = null,
+ indication = null,
+ onClick = viewModel::onOpenEnableWidgetDialog
+ ),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
@@ -841,14 +967,14 @@
if (!isEditMode || toolbarSize == null) {
return PaddingValues(start = 48.dp, end = 48.dp, top = Dimensions.GridTopSpacing)
}
- val configuration = LocalConfiguration.current
+ val context = LocalContext.current
val density = LocalDensity.current
- val screenHeight = configuration.screenHeightDp.dp
+ val windowMetrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context)
+ val screenHeight = with(density) { windowMetrics.bounds.height().toDp() }
val toolbarHeight = with(density) { Dimensions.ToolbarPaddingTop + toolbarSize.height.toDp() }
val verticalPadding =
- ((screenHeight - toolbarHeight - Dimensions.GridHeight) / 2).coerceAtLeast(
- Dimensions.Spacing
- )
+ ((screenHeight - toolbarHeight - Dimensions.GridHeight + Dimensions.GridTopSpacing) / 2)
+ .coerceAtLeast(Dimensions.Spacing)
return PaddingValues(
start = Dimensions.ToolbarPaddingHorizontal,
end = Dimensions.ToolbarPaddingHorizontal,
@@ -923,6 +1049,9 @@
horizontal = ToolbarButtonPaddingHorizontal,
)
val IconSize = 48.dp
+
+ val LockIconSize = 52.dp
+ val LockIconBottomPadding = 70.dp
}
private object Colors {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
index 3d88ad5..9e905ac 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
@@ -27,6 +27,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -38,6 +39,7 @@
@Inject
constructor(
private val viewModel: CommunalViewModel,
+ private val dialogFactory: SystemUIDialogFactory,
) : ComposableScene {
override val key = Scenes.Communal
@@ -51,6 +53,6 @@
@Composable
override fun SceneScope.Content(modifier: Modifier) {
- CommunalHub(modifier, viewModel)
+ CommunalHub(modifier, viewModel, dialogFactory)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/EnableWidgetDialog.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/EnableWidgetDialog.kt
new file mode 100644
index 0000000..df11206
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/EnableWidgetDialog.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.compose
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import com.android.compose.theme.LocalAndroidColorScheme
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.ComponentSystemUIDialog
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
+import com.android.systemui.statusbar.phone.create
+
+/** Dialog shown upon tapping a disabled widget which allows users to enable the widget. */
+@Composable
+fun EnableWidgetDialog(
+ isEnableWidgetDialogVisible: Boolean,
+ dialogFactory: SystemUIDialogFactory,
+ title: String,
+ positiveButtonText: String,
+ onConfirm: () -> Unit,
+ onCancel: () -> Unit
+) {
+ var dialog: ComponentSystemUIDialog? by remember { mutableStateOf(null) }
+ val context = LocalView.current.context
+
+ DisposableEffect(isEnableWidgetDialogVisible) {
+ if (isEnableWidgetDialogVisible) {
+ dialog =
+ dialogFactory.create(
+ context = context,
+ ) {
+ DialogComposable(title, positiveButtonText, onConfirm, onCancel)
+ }
+ dialog?.apply {
+ setCancelable(true)
+ setCanceledOnTouchOutside(true)
+ setOnCancelListener { onCancel() }
+ show()
+ }
+ }
+
+ onDispose {
+ dialog?.dismiss()
+ dialog = null
+ }
+ }
+}
+
+@Composable
+private fun DialogComposable(
+ title: String,
+ positiveButtonText: String,
+ onConfirm: () -> Unit,
+ onCancel: () -> Unit,
+) {
+ Box(
+ Modifier.fillMaxWidth()
+ .padding(top = 18.dp, bottom = 8.dp)
+ .background(LocalAndroidColorScheme.current.surfaceBright, RoundedCornerShape(28.dp))
+ ) {
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ verticalArrangement = Arrangement.spacedBy(20.dp),
+ ) {
+ Box(
+ modifier = Modifier.padding(horizontal = 24.dp).fillMaxWidth().wrapContentHeight(),
+ contentAlignment = Alignment.TopStart
+ ) {
+ Text(
+ text = title,
+ style = MaterialTheme.typography.titleMedium,
+ color = LocalAndroidColorScheme.current.onSurface,
+ textAlign = TextAlign.Center,
+ maxLines = 1,
+ )
+ }
+
+ Box(
+ modifier = Modifier.padding(end = 12.dp).fillMaxWidth().wrapContentHeight(),
+ contentAlignment = Alignment.Center
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.End,
+ ) {
+ TextButton(
+ contentPadding = PaddingValues(16.dp),
+ onClick = onCancel,
+ ) {
+ Text(
+ text = stringResource(R.string.cancel),
+ )
+ }
+ TextButton(
+ contentPadding = PaddingValues(16.dp),
+ onClick = onConfirm,
+ ) {
+ Text(
+ text = positiveButtonText,
+ )
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index beb8ddef..1adb335 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -41,7 +41,6 @@
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.toOffset
import androidx.compose.ui.unit.toSize
-import androidx.compose.ui.zIndex
import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
import com.android.systemui.communal.ui.compose.extensions.plus
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
@@ -247,7 +246,7 @@
content: @Composable (isDragging: Boolean) -> Unit
) {
if (!enabled) {
- return Box(modifier = modifier) { content(false) }
+ return content(false)
}
val dragging = index == dragDropState.draggingItemIndex
@@ -258,7 +257,7 @@
)
val draggingModifier =
if (dragging) {
- Modifier.zIndex(1f).graphicsLayer {
+ Modifier.graphicsLayer {
translationX = dragDropState.draggingItemOffset.x
translationY = dragDropState.draggingItemOffset.y
alpha = itemAlpha
@@ -268,13 +267,14 @@
}
Box(modifier) {
+ Box(draggingModifier) { content(dragging) }
AnimatedVisibility(
+ modifier = Modifier.matchParentSize(),
visible = (dragging || selected) && !dragDropState.isDraggingToRemove,
enter = fadeIn(),
exit = fadeOut()
) {
- HighlightedItem()
+ HighlightedItem(Modifier.matchParentSize())
}
- Box(modifier = draggingModifier, propagateMinConstraints = true) { content(dragging) }
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
index bc1e429..379c165 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
@@ -30,16 +30,18 @@
import kotlinx.coroutines.coroutineScope
/**
- * Observe taps without actually consuming them, so child elements can still respond to them. Long
+ * Observe taps without consuming them by default, so child elements can still respond to them. Long
* presses are excluded.
*/
-suspend fun PointerInputScope.observeTapsWithoutConsuming(
+suspend fun PointerInputScope.observeTaps(
pass: PointerEventPass = PointerEventPass.Initial,
+ shouldConsume: Boolean = false,
onTap: ((Offset) -> Unit)? = null,
) = coroutineScope {
if (onTap == null) return@coroutineScope
awaitEachGesture {
- awaitFirstDown(pass = pass)
+ val down = awaitFirstDown(pass = pass)
+ if (shouldConsume) down.consume()
val tapTimeout = viewConfiguration.longPressTimeoutMillis
val up = withTimeoutOrNull(tapTimeout) { waitForUpOrCancellation(pass = pass) }
if (up != null) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
index b5499b7..bc4e555 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
@@ -18,13 +18,16 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalView
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.animation.scene.transitions
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import javax.inject.Inject
@@ -40,6 +43,7 @@
constructor(
private val viewModel: LockscreenContentViewModel,
private val blueprints: Set<@JvmSuppressWildcards ComposableLockscreenSceneBlueprint>,
+ private val clockInteractor: KeyguardClockInteractor,
) {
private val sceneKeyByBlueprint: Map<ComposableLockscreenSceneBlueprint, SceneKey> by lazy {
@@ -55,6 +59,12 @@
) {
val coroutineScope = rememberCoroutineScope()
val blueprintId by viewModel.blueprintId(coroutineScope).collectAsState()
+ val view = LocalView.current
+ DisposableEffect(view) {
+ clockInteractor.clockEventController.registerListeners(view)
+
+ onDispose { clockInteractor.clockEventController.unregisterListeners() }
+ }
// Switch smoothly between blueprints, any composable tagged with element() will be
// transition-animated between any two blueprints, if they both display the same element.
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 a02781b..7acb4d5 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
@@ -19,52 +19,31 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import com.android.compose.animation.scene.Edge
-import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneScope
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
import dagger.Lazy
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
/** The lock screen scene shows when the device is locked. */
@SysUISingleton
class LockscreenScene
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
viewModel: LockscreenSceneViewModel,
private val lockscreenContent: Lazy<LockscreenContent>,
) : ComposableScene {
override val key = Scenes.Lockscreen
override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- combine(viewModel.upDestinationSceneKey, viewModel.leftDestinationSceneKey, ::Pair)
- .map { (upKey, leftKey) -> destinationScenes(up = upKey, left = leftKey) }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue =
- destinationScenes(
- up = viewModel.upDestinationSceneKey.value,
- left = viewModel.leftDestinationSceneKey.value,
- )
- )
+ viewModel.destinationScenes
@Composable
override fun SceneScope.Content(
@@ -75,19 +54,6 @@
modifier = modifier,
)
}
-
- private fun destinationScenes(
- up: SceneKey?,
- left: SceneKey?,
- ): Map<UserAction, UserActionResult> {
- return buildMap {
- up?.let { this[Swipe(SwipeDirection.Up)] = UserActionResult(up) }
- left?.let { this[Swipe(SwipeDirection.Left)] = UserActionResult(left) }
- this[Swipe(fromSource = Edge.Top, direction = SwipeDirection.Down)] =
- UserActionResult(Scenes.QuickSettings)
- this[Swipe(direction = SwipeDirection.Down)] = UserActionResult(Scenes.Shade)
- }
- }
}
@Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt
new file mode 100644
index 0000000..c5ff859
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.composable.blueprint
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.compose.animation.scene.transitions
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.largeClockElementKey
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smallClockElementKey
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smartspaceElementKey
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.ClockFaceInTransition.Companion.CLOCK_IN_MILLIS
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.ClockFaceInTransition.Companion.CLOCK_IN_START_DELAY_MILLIS
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.ClockFaceOutTransition.Companion.CLOCK_OUT_MILLIS
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.SmartspaceMoveTransition.Companion.STATUS_AREA_MOVE_DOWN_MILLIS
+import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition.SmartspaceMoveTransition.Companion.STATUS_AREA_MOVE_UP_MILLIS
+
+object ClockTransition {
+ val defaultClockTransitions = transitions {
+ from(ClockScenes.smallClockScene, to = ClockScenes.largeClockScene) {
+ transitioningToLargeClock()
+ }
+ from(ClockScenes.largeClockScene, to = ClockScenes.smallClockScene) {
+ transitioningToSmallClock()
+ }
+ }
+
+ private fun TransitionBuilder.transitioningToLargeClock() {
+ spec = tween(durationMillis = STATUS_AREA_MOVE_UP_MILLIS.toInt())
+ timestampRange(
+ startMillis = CLOCK_IN_START_DELAY_MILLIS.toInt(),
+ endMillis = (CLOCK_IN_START_DELAY_MILLIS + CLOCK_IN_MILLIS).toInt()
+ ) {
+ fade(largeClockElementKey)
+ }
+
+ timestampRange(endMillis = CLOCK_OUT_MILLIS.toInt()) { fade(smallClockElementKey) }
+ anchoredTranslate(smallClockElementKey, smartspaceElementKey)
+ }
+
+ private fun TransitionBuilder.transitioningToSmallClock() {
+ spec = tween(durationMillis = STATUS_AREA_MOVE_DOWN_MILLIS.toInt())
+ timestampRange(
+ startMillis = CLOCK_IN_START_DELAY_MILLIS.toInt(),
+ endMillis = (CLOCK_IN_START_DELAY_MILLIS + CLOCK_IN_MILLIS).toInt()
+ ) {
+ fade(smallClockElementKey)
+ }
+
+ timestampRange(endMillis = CLOCK_OUT_MILLIS.toInt()) { fade(largeClockElementKey) }
+ anchoredTranslate(smallClockElementKey, smartspaceElementKey)
+ }
+}
+
+object ClockScenes {
+ val smallClockScene = SceneKey("small-clock-scene")
+ val largeClockScene = SceneKey("large-clock-scene")
+}
+
+object ClockElementKeys {
+ val largeClockElementKey = ElementKey("large-clock")
+ val smallClockElementKey = ElementKey("small-clock")
+ val smartspaceElementKey = ElementKey("smart-space")
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index d23cd0c..9509fd2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -17,19 +17,14 @@
package com.android.systemui.keyguard.ui.composable.blueprint
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
-import com.android.compose.modifiers.padding
-import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -38,11 +33,8 @@
import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection
import com.android.systemui.keyguard.ui.composable.section.NotificationSection
import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
-import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
-import com.android.systemui.media.controls.ui.composable.MediaCarousel
-import com.android.systemui.res.R
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
@@ -59,14 +51,12 @@
private val viewModel: LockscreenContentViewModel,
private val statusBarSection: StatusBarSection,
private val clockSection: DefaultClockSection,
- private val smartSpaceSection: SmartSpaceSection,
private val notificationSection: NotificationSection,
private val lockSection: LockSection,
private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
private val bottomAreaSection: BottomAreaSection,
private val settingsMenuSection: SettingsMenuSection,
private val mediaCarouselSection: MediaCarouselSection,
- private val clockInteractor: KeyguardClockInteractor,
) : ComposableLockscreenSceneBlueprint {
override val id: String = "default"
@@ -74,7 +64,6 @@
@Composable
override fun SceneScope.Content(modifier: Modifier) {
val isUdfpsVisible = viewModel.isUdfpsVisible
- val burnIn = rememberBurnIn(clockInteractor)
val resources = LocalContext.current.resources
LockscreenLongPress(
@@ -88,40 +77,7 @@
modifier = Modifier.fillMaxWidth(),
) {
with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
- with(clockSection) {
- SmallClock(
- burnInParams = burnIn.parameters,
- onTopChanged = burnIn.onSmallClockTopChanged,
- modifier = Modifier.fillMaxWidth(),
- )
- }
- with(smartSpaceSection) {
- SmartSpace(
- burnInParams = burnIn.parameters,
- onTopChanged = burnIn.onSmartspaceTopChanged,
- modifier =
- Modifier.fillMaxWidth()
- .padding(
- top = { viewModel.getSmartSpacePaddingTop(resources) },
- )
- .padding(
- bottom =
- dimensionResource(
- R.dimen.keyguard_status_view_bottom_margin
- ),
- ),
- )
- }
-
- if (viewModel.isLargeClockVisible) {
- Spacer(modifier = Modifier.weight(weight = 1f))
- with(clockSection) {
- LargeClock(
- modifier = Modifier.fillMaxWidth(),
- )
- }
- }
-
+ with(clockSection) { DefaultClockLayout() }
with(mediaCarouselSection) { MediaCarousel() }
if (viewModel.areNotificationsVisible(resources = resources)) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index c422c4b..9abfa42 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -17,19 +17,14 @@
package com.android.systemui.keyguard.ui.composable.blueprint
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
-import com.android.compose.modifiers.padding
-import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -38,10 +33,8 @@
import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection
import com.android.systemui.keyguard.ui.composable.section.NotificationSection
import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
-import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
-import com.android.systemui.res.R
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
@@ -58,14 +51,12 @@
private val viewModel: LockscreenContentViewModel,
private val statusBarSection: StatusBarSection,
private val clockSection: DefaultClockSection,
- private val smartSpaceSection: SmartSpaceSection,
private val notificationSection: NotificationSection,
private val lockSection: LockSection,
private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
private val bottomAreaSection: BottomAreaSection,
private val settingsMenuSection: SettingsMenuSection,
private val mediaCarouselSection: MediaCarouselSection,
- private val clockInteractor: KeyguardClockInteractor,
) : ComposableLockscreenSceneBlueprint {
override val id: String = "shortcuts-besides-udfps"
@@ -73,7 +64,6 @@
@Composable
override fun SceneScope.Content(modifier: Modifier) {
val isUdfpsVisible = viewModel.isUdfpsVisible
- val burnIn = rememberBurnIn(clockInteractor)
val resources = LocalContext.current.resources
LockscreenLongPress(
@@ -87,36 +77,7 @@
modifier = Modifier.fillMaxWidth(),
) {
with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
- with(clockSection) {
- SmallClock(
- onTopChanged = burnIn.onSmallClockTopChanged,
- modifier = Modifier.fillMaxWidth(),
- burnInParams = burnIn.parameters,
- )
- }
- with(smartSpaceSection) {
- SmartSpace(
- burnInParams = burnIn.parameters,
- onTopChanged = burnIn.onSmartspaceTopChanged,
- modifier =
- Modifier.fillMaxWidth()
- .padding(
- top = { viewModel.getSmartSpacePaddingTop(resources) }
- )
- .padding(
- bottom =
- dimensionResource(
- R.dimen.keyguard_status_view_bottom_margin
- )
- ),
- )
- }
-
- if (viewModel.isLargeClockVisible) {
- Spacer(modifier = Modifier.weight(weight = 1f))
- with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
- }
-
+ with(clockSection) { DefaultClockLayout() }
with(mediaCarouselSection) { MediaCarousel() }
if (viewModel.areNotificationsVisible(resources = resources)) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
index d218425..652412d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
@@ -18,7 +18,6 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -27,7 +26,6 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntRect
@@ -35,7 +33,6 @@
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.padding
import com.android.systemui.Flags
-import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -44,7 +41,6 @@
import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection
import com.android.systemui.keyguard.ui.composable.section.NotificationSection
import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
-import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import com.android.systemui.res.R
@@ -65,14 +61,12 @@
private val viewModel: LockscreenContentViewModel,
private val statusBarSection: StatusBarSection,
private val clockSection: DefaultClockSection,
- private val smartSpaceSection: SmartSpaceSection,
private val notificationSection: NotificationSection,
private val lockSection: LockSection,
private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
private val bottomAreaSection: BottomAreaSection,
private val settingsMenuSection: SettingsMenuSection,
private val mediaCarouselSection: MediaCarouselSection,
- private val clockInteractor: KeyguardClockInteractor,
private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
) : ComposableLockscreenSceneBlueprint {
@@ -81,8 +75,6 @@
@Composable
override fun SceneScope.Content(modifier: Modifier) {
val isUdfpsVisible = viewModel.isUdfpsVisible
- val burnIn = rememberBurnIn(clockInteractor)
- val resources = LocalContext.current.resources
LockscreenLongPress(
viewModel = viewModel.longPress,
@@ -102,41 +94,7 @@
modifier = Modifier.fillMaxHeight().weight(weight = 1f),
horizontalAlignment = Alignment.CenterHorizontally,
) {
- with(clockSection) {
- SmallClock(
- burnInParams = burnIn.parameters,
- onTopChanged = burnIn.onSmallClockTopChanged,
- modifier = Modifier.fillMaxWidth(),
- )
- }
-
- with(smartSpaceSection) {
- SmartSpace(
- burnInParams = burnIn.parameters,
- onTopChanged = burnIn.onSmartspaceTopChanged,
- modifier =
- Modifier.fillMaxWidth()
- .padding(
- top = {
- viewModel.getSmartSpacePaddingTop(resources)
- },
- )
- .padding(
- bottom =
- dimensionResource(
- R.dimen
- .keyguard_status_view_bottom_margin
- )
- ),
- )
- }
-
- if (viewModel.isLargeClockVisible) {
- Spacer(modifier = Modifier.weight(weight = 1f))
- with(clockSection) { LargeClock() }
- Spacer(modifier = Modifier.weight(weight = 1f))
- }
-
+ with(clockSection) { DefaultClockLayout() }
with(mediaCarouselSection) { MediaCarousel() }
}
with(notificationSection) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
index 0728daf..2a99039 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
@@ -26,6 +26,7 @@
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.BurnInScaleViewModel
+import kotlinx.coroutines.flow.map
/**
* Modifies the composable to account for anti-burn in translation, alpha, and scaling.
@@ -38,9 +39,18 @@
params: BurnInParameters,
isClock: Boolean = false,
): Modifier {
- val translationX by viewModel.translationX(params).collectAsState(initial = 0f)
- val translationY by viewModel.translationY(params).collectAsState(initial = 0f)
- val scaleViewModel by viewModel.scale(params).collectAsState(initial = BurnInScaleViewModel())
+ val burnIn = viewModel.movement(params)
+ val translationX by burnIn.map { it.translationX.toFloat() }.collectAsState(initial = 0f)
+ val translationY by burnIn.map { it.translationY.toFloat() }.collectAsState(initial = 0f)
+ val scaleViewModel by
+ burnIn
+ .map {
+ BurnInScaleViewModel(
+ scale = it.scale,
+ scaleClockOnly = it.scaleClockOnly,
+ )
+ }
+ .collectAsState(initial = BurnInScaleViewModel())
return this.graphicsLayer {
val scale =
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
index 152cc67..1ab2bc76 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
@@ -18,27 +18,37 @@
import android.view.ViewGroup
import android.widget.FrameLayout
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.viewinterop.AndroidView
-import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.modifiers.padding
-import com.android.keyguard.KeyguardClockSwitch
import com.android.systemui.customization.R as customizationR
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.largeClockElementKey
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smallClockElementKey
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smartspaceElementKey
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.smallClockScene
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockTransition.defaultClockTransitions
+import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn
import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
import com.android.systemui.keyguard.ui.composable.modifier.onTopPlacementChanged
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
+import com.android.systemui.res.R
import javax.inject.Inject
/** Provides small clock and large clock composables for the default clock face. */
@@ -48,112 +58,152 @@
private val viewModel: KeyguardClockViewModel,
private val clockInteractor: KeyguardClockInteractor,
private val aodBurnInViewModel: AodBurnInViewModel,
+ private val lockscreenContentViewModel: LockscreenContentViewModel,
+ private val smartSpaceSection: SmartSpaceSection,
) {
+ @Composable
+ fun DefaultClockLayout(
+ modifier: Modifier = Modifier,
+ ) {
+ val isLargeClockVisible by viewModel.isLargeClockVisible.collectAsState()
+ val burnIn = rememberBurnIn(clockInteractor)
+ val currentScene =
+ if (isLargeClockVisible) {
+ largeClockScene
+ } else {
+ smallClockScene
+ }
+
+ LaunchedEffect(isLargeClockVisible) {
+ if (isLargeClockVisible) {
+ burnIn.onSmallClockTopChanged(null)
+ }
+ }
+
+ SceneTransitionLayout(
+ modifier = modifier,
+ currentScene = currentScene,
+ onChangeScene = {},
+ transitions = defaultClockTransitions,
+ ) {
+ scene(smallClockScene) {
+ Column {
+ SmallClock(
+ burnInParams = burnIn.parameters,
+ onTopChanged = burnIn.onSmallClockTopChanged,
+ modifier = Modifier.element(smallClockElementKey).fillMaxWidth()
+ )
+ SmartSpaceContent()
+ }
+ }
+
+ scene(largeClockScene) {
+ Column {
+ SmartSpaceContent()
+ LargeClock(modifier = Modifier.element(largeClockElementKey).fillMaxWidth())
+ }
+ }
+ }
+ }
@Composable
- fun SceneScope.SmallClock(
+ private fun SceneScope.SmartSpaceContent(
+ modifier: Modifier = Modifier,
+ ) {
+ val burnIn = rememberBurnIn(clockInteractor)
+ val resources = LocalContext.current.resources
+
+ MovableElement(key = smartspaceElementKey, modifier = modifier) {
+ content {
+ with(smartSpaceSection) {
+ this@SmartSpaceContent.SmartSpace(
+ burnInParams = burnIn.parameters,
+ onTopChanged = burnIn.onSmartspaceTopChanged,
+ modifier =
+ Modifier.fillMaxWidth()
+ .padding(
+ top = {
+ lockscreenContentViewModel.getSmartSpacePaddingTop(
+ resources
+ )
+ },
+ bottom = {
+ resources.getDimensionPixelSize(
+ R.dimen.keyguard_status_view_bottom_margin
+ )
+ }
+ ),
+ )
+ }
+ }
+ }
+ }
+
+ @Composable
+ private fun SceneScope.SmallClock(
burnInParams: BurnInParameters,
onTopChanged: (top: Float?) -> Unit,
modifier: Modifier = Modifier,
) {
- val clockSize by viewModel.clockSize.collectAsState()
val currentClock by viewModel.currentClock.collectAsState()
- viewModel.clock = currentClock
-
- if (clockSize != KeyguardClockSwitch.SMALL || currentClock?.smallClock?.view == null) {
- onTopChanged(null)
+ if (currentClock?.smallClock?.view == null) {
return
}
+ viewModel.clock = currentClock
- val view = LocalView.current
+ val context = LocalContext.current
- DisposableEffect(view) {
- clockInteractor.clockEventController.registerListeners(view)
-
- onDispose { clockInteractor.clockEventController.unregisterListeners() }
- }
-
- MovableElement(
- key = ClockElementKey,
- modifier = modifier,
- ) {
- content {
- AndroidView(
- factory = { context ->
- FrameLayout(context).apply {
- val newClockView = checkNotNull(currentClock).smallClock.view
- (newClockView.parent as? ViewGroup)?.removeView(newClockView)
- addView(newClockView)
- }
- },
- modifier =
- Modifier.padding(
- horizontal =
- dimensionResource(customizationR.dimen.clock_padding_start)
- )
- .padding(top = { viewModel.getSmallClockTopMargin(view.context) })
- .onTopPlacementChanged(onTopChanged)
- .burnInAware(
- viewModel = aodBurnInViewModel,
- params = burnInParams,
- ),
- update = {
- val newClockView = checkNotNull(currentClock).smallClock.view
- it.removeAllViews()
- (newClockView.parent as? ViewGroup)?.removeView(newClockView)
- it.addView(newClockView)
- },
- )
- }
- }
+ AndroidView(
+ factory = { context ->
+ FrameLayout(context).apply {
+ val newClockView = checkNotNull(currentClock).smallClock.view
+ (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+ addView(newClockView)
+ }
+ },
+ update = {
+ val newClockView = checkNotNull(currentClock).smallClock.view
+ it.removeAllViews()
+ (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+ it.addView(newClockView)
+ },
+ modifier =
+ modifier
+ .padding(
+ horizontal = dimensionResource(customizationR.dimen.clock_padding_start)
+ )
+ .padding(top = { viewModel.getSmallClockTopMargin(context) })
+ .onTopPlacementChanged(onTopChanged)
+ .burnInAware(
+ viewModel = aodBurnInViewModel,
+ params = burnInParams,
+ ),
+ )
}
@Composable
- fun SceneScope.LargeClock(modifier: Modifier = Modifier) {
- val clockSize by viewModel.clockSize.collectAsState()
+ private fun SceneScope.LargeClock(modifier: Modifier = Modifier) {
val currentClock by viewModel.currentClock.collectAsState()
viewModel.clock = currentClock
-
- if (clockSize != KeyguardClockSwitch.LARGE) {
- return
- }
-
if (currentClock?.largeClock?.view == null) {
return
}
- val view = LocalView.current
-
- DisposableEffect(view) {
- clockInteractor.clockEventController.registerListeners(view)
-
- onDispose { clockInteractor.clockEventController.unregisterListeners() }
- }
-
- MovableElement(
- key = ClockElementKey,
- modifier = modifier,
- ) {
- content {
- AndroidView(
- factory = { context ->
- FrameLayout(context).apply {
- val newClockView = checkNotNull(currentClock).largeClock.view
- (newClockView.parent as? ViewGroup)?.removeView(newClockView)
- addView(newClockView)
- }
- },
- update = {
- val newClockView = checkNotNull(currentClock).largeClock.view
- it.removeAllViews()
- (newClockView.parent as? ViewGroup)?.removeView(newClockView)
- it.addView(newClockView)
- },
- modifier = Modifier.fillMaxSize()
- )
- }
- }
+ AndroidView(
+ factory = { context ->
+ FrameLayout(context).apply {
+ val newClockView = checkNotNull(currentClock).largeClock.view
+ (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+ addView(newClockView)
+ }
+ },
+ update = {
+ val newClockView = checkNotNull(currentClock).largeClock.view
+ it.removeAllViews()
+ (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+ it.addView(newClockView)
+ },
+ modifier = modifier.fillMaxSize()
+ )
}
}
-
-private val ClockElementKey = ElementKey("Clock")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
index 9b71844..d1cc53e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
@@ -33,7 +33,6 @@
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
-import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
@@ -60,7 +59,7 @@
modifier: Modifier = Modifier,
) {
Column(
- modifier = modifier.element(SmartSpaceElementKey).onTopPlacementChanged(onTopChanged),
+ modifier = modifier.onTopPlacementChanged(onTopChanged),
) {
if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) {
return
@@ -192,5 +191,3 @@
)
}
}
-
-private val SmartSpaceElementKey = ElementKey("SmartSpace")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index eb71490..5d9b014 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.footer.ui.compose
+import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.LocalIndication
@@ -76,9 +77,31 @@
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.qs.ui.composable.QuickSettingsTheme
import com.android.systemui.res.R
import kotlinx.coroutines.launch
+@Composable
+fun FooterActionsWithAnimatedVisibility(
+ viewModel: FooterActionsViewModel,
+ isCustomizing: Boolean,
+ lifecycleOwner: LifecycleOwner,
+ footerActionsModifier: (Modifier) -> Modifier,
+ modifier: Modifier = Modifier,
+) {
+ AnimatedVisibility(visible = !isCustomizing, modifier = modifier.fillMaxWidth()) {
+ QuickSettingsTheme {
+ // This view has its own horizontal padding
+ // TODO(b/321716470) This should use a lifecycle tied to the scene.
+ FooterActions(
+ viewModel = viewModel,
+ qsVisibilityLifecycleOwner = lifecycleOwner,
+ modifier = footerActionsModifier(Modifier),
+ )
+ }
+ }
+}
+
/** The Quick Settings footer actions row. */
@Composable
fun FooterActions(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index 91b737d..bc48dd1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -63,12 +63,14 @@
}
private fun SceneScope.stateForQuickSettingsContent(
+ isSplitShade: Boolean,
squishiness: Float = QuickSettings.SharedValues.SquishinessValues.Default
): QSSceneAdapter.State {
return when (val transitionState = layoutState.transitionState) {
is TransitionState.Idle -> {
when (transitionState.currentScene) {
- Scenes.Shade -> QSSceneAdapter.State.QQS
+ Scenes.Shade -> QSSceneAdapter.State.QQS.takeUnless { isSplitShade }
+ ?: QSSceneAdapter.State.QS
Scenes.QuickSettings -> QSSceneAdapter.State.QS
else -> QSSceneAdapter.State.CLOSED
}
@@ -76,6 +78,7 @@
is TransitionState.Transition ->
with(transitionState) {
when {
+ isSplitShade -> QSSceneAdapter.State.QS
fromScene == Scenes.Shade && toScene == Scenes.QuickSettings ->
Expanding(progress)
fromScene == Scenes.QuickSettings && toScene == Scenes.Shade ->
@@ -111,10 +114,11 @@
fun SceneScope.QuickSettings(
qsSceneAdapter: QSSceneAdapter,
heightProvider: () -> Int,
+ isSplitShade: Boolean,
modifier: Modifier = Modifier,
squishiness: Float = QuickSettings.SharedValues.SquishinessValues.Default,
) {
- val contentState = stateForQuickSettingsContent(squishiness)
+ val contentState = stateForQuickSettingsContent(isSplitShade, squishiness)
MovableElement(
key = QuickSettings.Elements.Content,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 3b8b863..6ae1410 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -61,6 +61,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.qs.footer.ui.compose.FooterActions
+import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibility
import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
@@ -238,24 +239,21 @@
QuickSettings(
viewModel.qsSceneAdapter,
{ viewModel.qsSceneAdapter.qsHeight },
+ isSplitShade = false,
modifier = Modifier.sysuiResTag("expanded_qs_scroll_view"),
)
}
}
- AnimatedVisibility(
- visible = !isCustomizing,
- modifier = Modifier.align(Alignment.CenterHorizontally).fillMaxWidth()
- ) {
- QuickSettingsTheme {
- // This view has its own horizontal padding
- // TODO(b/321716470) This should use a lifecycle tied to the scene.
- FooterActions(
- viewModel = footerActionsViewModel,
- qsVisibilityLifecycleOwner = lifecycleOwner,
- modifier = Modifier.element(QuickSettings.Elements.FooterActions)
- )
- }
- }
+
+ FooterActionsWithAnimatedVisibility(
+ viewModel = footerActionsViewModel,
+ isCustomizing = isCustomizing,
+ lifecycleOwner = lifecycleOwner,
+ footerActionsModifier = { modifier ->
+ modifier.element(QuickSettings.Elements.FooterActions)
+ },
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ )
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index 82f56ab..975829a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -20,21 +20,16 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.SceneScope
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
+import com.android.systemui.scene.ui.viewmodel.GoneSceneViewModel
import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
/**
* "Gone" is not a real scene but rather the absence of scenes when we want to skip showing any
@@ -44,22 +39,12 @@
class GoneScene
@Inject
constructor(
- private val notificationsViewModel: NotificationsPlaceholderViewModel,
+ private val viewModel: GoneSceneViewModel,
) : ComposableScene {
override val key = Scenes.Gone
override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- MutableStateFlow<Map<UserAction, UserActionResult>>(
- mapOf(
- Swipe(
- pointerCount = 2,
- fromSource = Edge.Top,
- direction = SwipeDirection.Down,
- ) to UserActionResult(Scenes.QuickSettings),
- Swipe(direction = SwipeDirection.Down) to UserActionResult(Scenes.Shade),
- )
- )
- .asStateFlow()
+ viewModel.destinationScenes
@Composable
override fun SceneScope.Content(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 3620cc5..31201c2f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -19,14 +19,26 @@
import android.view.ViewGroup
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.clipScrollableContainer
+import androidx.compose.foundation.gestures.Orientation
+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.WindowInsets
+import androidx.compose.foundation.layout.asPaddingValues
+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.navigationBars
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -36,22 +48,19 @@
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.LowestZIndexScenePicker
-import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneScope
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.compose.modifiers.thenIf
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.media.controls.ui.controller.MediaCarouselController
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
@@ -59,10 +68,12 @@
import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
import com.android.systemui.notifications.ui.composable.NotificationScrollingStack
+import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibility
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager
@@ -71,11 +82,7 @@
import javax.inject.Inject
import javax.inject.Named
import kotlin.math.roundToInt
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
object Shade {
object Elements {
@@ -103,7 +110,6 @@
class ShadeScene
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
private val viewModel: ShadeSceneViewModel,
private val tintedIconManagerFactory: TintedIconManager.Factory,
private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
@@ -114,13 +120,7 @@
override val key = Scenes.Shade
override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- viewModel.upDestinationSceneKey
- .map { sceneKey -> destinationScenes(up = sceneKey) }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue = destinationScenes(up = viewModel.upDestinationSceneKey.value),
- )
+ viewModel.destinationScenes
@Composable
override fun SceneScope.Content(
@@ -141,15 +141,6 @@
mediaHost.showsOnlyActiveMedia = true
mediaHost.init(MediaHierarchyManager.LOCATION_QQS)
}
-
- private fun destinationScenes(
- up: SceneKey,
- ): Map<UserAction, UserActionResult> {
- return mapOf(
- Swipe(SwipeDirection.Up) to UserActionResult(up),
- Swipe(SwipeDirection.Down) to UserActionResult(Scenes.QuickSettings),
- )
- }
}
@Composable
@@ -162,8 +153,42 @@
mediaHost: MediaHost,
modifier: Modifier = Modifier,
) {
- val density = LocalDensity.current
- val layoutWidth = remember { mutableStateOf(0) }
+ val shadeMode by viewModel.shadeMode.collectAsState()
+ when (shadeMode) {
+ is ShadeMode.Single ->
+ SingleShade(
+ viewModel = viewModel,
+ createTintedIconManager = createTintedIconManager,
+ createBatteryMeterViewController = createBatteryMeterViewController,
+ statusBarIconController = statusBarIconController,
+ mediaCarouselController = mediaCarouselController,
+ mediaHost = mediaHost,
+ modifier = modifier,
+ )
+ is ShadeMode.Split ->
+ SplitShade(
+ viewModel = viewModel,
+ createTintedIconManager = createTintedIconManager,
+ createBatteryMeterViewController = createBatteryMeterViewController,
+ statusBarIconController = statusBarIconController,
+ mediaCarouselController = mediaCarouselController,
+ mediaHost = mediaHost,
+ modifier = modifier,
+ )
+ is ShadeMode.Dual -> error("Dual shade is not yet implemented!")
+ }
+}
+
+@Composable
+private fun SceneScope.SingleShade(
+ viewModel: ShadeSceneViewModel,
+ createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
+ createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
+ statusBarIconController: StatusBarIconController,
+ mediaCarouselController: MediaCarouselController,
+ mediaHost: MediaHost,
+ modifier: Modifier = Modifier,
+) {
val maxNotifScrimTop = remember { mutableStateOf(0f) }
val tileSquishiness by
animateSceneFloatAsState(value = 1f, key = QuickSettings.SharedValues.TilesSquishiness)
@@ -203,38 +228,15 @@
(viewModel.qsSceneAdapter.qqsHeight * tileSquishiness)
.roundToInt()
},
+ isSplitShade = false,
squishiness = tileSquishiness,
)
- if (viewModel.isMediaVisible()) {
- val mediaHeight =
- dimensionResource(R.dimen.qs_media_session_height_expanded)
- MediaCarousel(
- modifier =
- Modifier.height(mediaHeight).fillMaxWidth().layout {
- measurable,
- constraints ->
- val placeable = measurable.measure(constraints)
-
- // Notify controller to size the carousel for the
- // current space
- mediaHost.measurementInput =
- MeasurementInput(placeable.width, placeable.height)
- mediaCarouselController.setSceneContainerSize(
- placeable.width,
- placeable.height
- )
-
- layout(placeable.width, placeable.height) {
- placeable.placeRelative(0, 0)
- }
- },
- mediaHost = mediaHost,
- layoutWidth = layoutWidth.value,
- layoutHeight = with(density) { mediaHeight.toPx() }.toInt(),
- carouselController = mediaCarouselController,
- )
- }
+ MediaIfVisible(
+ viewModel = viewModel,
+ mediaCarouselController = mediaCarouselController,
+ mediaHost = mediaHost,
+ )
Spacer(modifier = Modifier.height(16.dp))
}
@@ -263,3 +265,132 @@
}
}
}
+
+@Composable
+private fun SceneScope.SplitShade(
+ viewModel: ShadeSceneViewModel,
+ createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
+ createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
+ statusBarIconController: StatusBarIconController,
+ mediaCarouselController: MediaCarouselController,
+ mediaHost: MediaHost,
+ modifier: Modifier = Modifier,
+) {
+ val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
+ val lifecycleOwner = LocalLifecycleOwner.current
+ val footerActionsViewModel =
+ remember(lifecycleOwner, viewModel) { viewModel.getFooterActionsViewModel(lifecycleOwner) }
+
+ val navBarBottomHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
+ val density = LocalDensity.current
+ LaunchedEffect(navBarBottomHeight, density) {
+ with(density) {
+ viewModel.qsSceneAdapter.applyBottomNavBarPadding(navBarBottomHeight.roundToPx())
+ }
+ }
+
+ val quickSettingsScrollState = rememberScrollState()
+ LaunchedEffect(isCustomizing, quickSettingsScrollState) {
+ if (isCustomizing) {
+ quickSettingsScrollState.scrollTo(0)
+ }
+ }
+
+ Box(
+ modifier =
+ modifier
+ .fillMaxSize()
+ .element(Shade.Elements.BackgroundScrim)
+ .background(colorResource(R.color.shade_scrim_background_dark))
+ ) {
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ ) {
+ CollapsedShadeHeader(
+ viewModel = viewModel.shadeHeaderViewModel,
+ createTintedIconManager = createTintedIconManager,
+ createBatteryMeterViewController = createBatteryMeterViewController,
+ statusBarIconController = statusBarIconController,
+ modifier = Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding)
+ )
+
+ Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
+ Column(
+ verticalArrangement = Arrangement.Top,
+ modifier =
+ Modifier.weight(1f).fillMaxHeight().thenIf(!isCustomizing) {
+ Modifier.verticalNestedScrollToScene()
+ .verticalScroll(quickSettingsScrollState)
+ .clipScrollableContainer(Orientation.Horizontal)
+ .padding(bottom = navBarBottomHeight)
+ }
+ ) {
+ QuickSettings(
+ qsSceneAdapter = viewModel.qsSceneAdapter,
+ heightProvider = { viewModel.qsSceneAdapter.qsHeight },
+ isSplitShade = true,
+ modifier = Modifier.fillMaxWidth(),
+ )
+
+ MediaIfVisible(
+ viewModel = viewModel,
+ mediaCarouselController = mediaCarouselController,
+ mediaHost = mediaHost,
+ modifier = Modifier.fillMaxWidth(),
+ )
+
+ Spacer(
+ modifier = Modifier.weight(1f),
+ )
+
+ FooterActionsWithAnimatedVisibility(
+ viewModel = footerActionsViewModel,
+ isCustomizing = isCustomizing,
+ lifecycleOwner = lifecycleOwner,
+ footerActionsModifier = { modifier ->
+ modifier.element(QuickSettings.Elements.FooterActions)
+ },
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ )
+ }
+
+ NotificationScrollingStack(
+ viewModel = viewModel.notifications,
+ maxScrimTop = { 0f },
+ modifier = Modifier.weight(1f).fillMaxHeight(),
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun SceneScope.MediaIfVisible(
+ viewModel: ShadeSceneViewModel,
+ mediaCarouselController: MediaCarouselController,
+ mediaHost: MediaHost,
+ modifier: Modifier = Modifier,
+) {
+ if (viewModel.isMediaVisible()) {
+ val density = LocalDensity.current
+ val mediaHeight = dimensionResource(R.dimen.qs_media_session_height_expanded)
+
+ MediaCarousel(
+ modifier =
+ modifier.height(mediaHeight).fillMaxWidth().layout { measurable, constraints ->
+ val placeable = measurable.measure(constraints)
+
+ // Notify controller to size the carousel for the
+ // current space
+ mediaHost.measurementInput = MeasurementInput(placeable.width, placeable.height)
+ mediaCarouselController.setSceneContainerSize(placeable.width, placeable.height)
+
+ layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) }
+ },
+ mediaHost = mediaHost,
+ layoutWidth = 0,
+ layoutHeight = with(density) { mediaHeight.toPx() }.toInt(),
+ carouselController = mediaCarouselController,
+ )
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
index 8ac84ff..b1fbe35 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.panel.component.anc.ui.composable
import android.content.Context
+import android.view.ContextThemeWrapper
import android.view.View
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.MaterialTheme
@@ -73,15 +74,16 @@
AndroidView<SliceView>(
modifier = Modifier.fillMaxWidth(),
factory = { context: Context ->
- SliceView(context).apply {
- mode = SliceView.MODE_LARGE
- isScrollable = false
- importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
- setShowTitleItems(true)
- addOnLayoutChangeListener(
- OnWidthChangedLayoutListener(viewModel::changeSliceWidth)
- )
- }
+ SliceView(ContextThemeWrapper(context, R.style.Widget_SliceView_VolumePanel))
+ .apply {
+ mode = SliceView.MODE_LARGE
+ isScrollable = false
+ importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+ setShowTitleItems(true)
+ addOnLayoutChangeListener(
+ OnWidthChangedLayoutListener(viewModel::changeSliceWidth)
+ )
+ }
},
update = { sliceView: SliceView -> sliceView.slice = slice }
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
new file mode 100644
index 0000000..ae267e2
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.selector.ui.composable
+
+import androidx.compose.animation.core.animateOffsetAsState
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.IntrinsicSize
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.CornerSize
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.dp
+
+/**
+ * Radio button group for the Volume Panel. It allows selecting a single item
+ *
+ * @param indicatorBackgroundPadding is the distance between the edge of the indicator and the
+ * indicator background
+ * @param labelIndicatorBackgroundSpacing is the distance between indicator background and labels
+ * row
+ */
+@Composable
+fun VolumePanelRadioButtonBar(
+ modifier: Modifier = Modifier,
+ indicatorBackgroundPadding: Dp =
+ VolumePanelRadioButtonBarDefaults.DefaultIndicatorBackgroundPadding,
+ spacing: Dp = VolumePanelRadioButtonBarDefaults.DefaultSpacing,
+ labelIndicatorBackgroundSpacing: Dp =
+ VolumePanelRadioButtonBarDefaults.DefaultLabelIndicatorBackgroundSpacing,
+ indicatorCornerRadius: CornerRadius =
+ VolumePanelRadioButtonBarDefaults.defaultIndicatorCornerRadius(),
+ indicatorBackgroundCornerSize: CornerSize =
+ CornerSize(VolumePanelRadioButtonBarDefaults.DefaultIndicatorBackgroundCornerRadius),
+ colors: VolumePanelRadioButtonBarColors = VolumePanelRadioButtonBarDefaults.defaultColors(),
+ content: VolumePanelRadioButtonBarScope.() -> Unit
+) {
+ val scope =
+ VolumePanelRadioButtonBarScopeImpl().apply(content).apply {
+ require(hasSelectedItem) { "At least one item should be selected" }
+ }
+
+ val items = scope.items
+
+ var selectedIndex by remember { mutableIntStateOf(items.indexOfFirst { it.isSelected }) }
+
+ var size by remember { mutableStateOf(IntSize(0, 0)) }
+ val spacingPx = with(LocalDensity.current) { spacing.toPx() }
+ val indicatorWidth = size.width / items.size - (spacingPx * (items.size - 1) / items.size)
+ val offset by
+ animateOffsetAsState(
+ targetValue =
+ Offset(
+ selectedIndex * indicatorWidth + (spacingPx * selectedIndex),
+ 0f,
+ ),
+ label = "VolumePanelRadioButtonOffsetAnimation",
+ finishedListener = {
+ for (itemIndex in items.indices) {
+ val item = items[itemIndex]
+ if (itemIndex == selectedIndex) {
+ item.onItemSelected()
+ break
+ }
+ }
+ }
+ )
+
+ Column(modifier = modifier) {
+ Box(modifier = Modifier.height(IntrinsicSize.Max)) {
+ Canvas(
+ modifier =
+ Modifier.fillMaxSize()
+ .background(
+ colors.indicatorBackgroundColor,
+ RoundedCornerShape(indicatorBackgroundCornerSize),
+ )
+ .padding(indicatorBackgroundPadding)
+ .onGloballyPositioned { size = it.size }
+ ) {
+ drawRoundRect(
+ color = colors.indicatorColor,
+ topLeft = offset,
+ size = Size(indicatorWidth, size.height.toFloat()),
+ cornerRadius = indicatorCornerRadius,
+ )
+ }
+ Row(
+ modifier = Modifier.padding(indicatorBackgroundPadding),
+ horizontalArrangement = Arrangement.spacedBy(spacing)
+ ) {
+ for (itemIndex in items.indices) {
+ TextButton(
+ modifier = Modifier.weight(1f),
+ onClick = { selectedIndex = itemIndex },
+ ) {
+ val item = items[itemIndex]
+ if (item.icon !== Empty) {
+ with(items[itemIndex]) { icon() }
+ }
+ }
+ }
+ }
+ }
+
+ Row(
+ modifier =
+ Modifier.padding(
+ start = indicatorBackgroundPadding,
+ top = labelIndicatorBackgroundSpacing,
+ end = indicatorBackgroundPadding
+ ),
+ horizontalArrangement = Arrangement.spacedBy(spacing),
+ ) {
+ for (itemIndex in items.indices) {
+ TextButton(
+ modifier = Modifier.weight(1f),
+ onClick = { selectedIndex = itemIndex },
+ ) {
+ val item = items[itemIndex]
+ if (item.icon !== Empty) {
+ with(items[itemIndex]) { label() }
+ }
+ }
+ }
+ }
+ }
+}
+
+data class VolumePanelRadioButtonBarColors(
+ /** Color of the indicator. */
+ val indicatorColor: Color,
+ /** Color of the indicator background. */
+ val indicatorBackgroundColor: Color,
+)
+
+object VolumePanelRadioButtonBarDefaults {
+
+ val DefaultIndicatorBackgroundPadding = 8.dp
+ val DefaultSpacing = 24.dp
+ val DefaultLabelIndicatorBackgroundSpacing = 12.dp
+ val DefaultIndicatorCornerRadius = 20.dp
+ val DefaultIndicatorBackgroundCornerRadius = 20.dp
+
+ @Composable
+ fun defaultIndicatorCornerRadius(
+ x: Dp = DefaultIndicatorCornerRadius,
+ y: Dp = DefaultIndicatorCornerRadius,
+ ): CornerRadius = with(LocalDensity.current) { CornerRadius(x.toPx(), y.toPx()) }
+
+ /**
+ * Returns the default VolumePanelRadioButtonBar colors.
+ *
+ * @param indicatorColor is the color of the indicator
+ * @param indicatorBackgroundColor is the color of the indicator background
+ */
+ @Composable
+ fun defaultColors(
+ indicatorColor: Color = MaterialTheme.colorScheme.primaryContainer,
+ indicatorBackgroundColor: Color = MaterialTheme.colorScheme.surface,
+ ): VolumePanelRadioButtonBarColors =
+ VolumePanelRadioButtonBarColors(
+ indicatorColor = indicatorColor,
+ indicatorBackgroundColor = indicatorBackgroundColor,
+ )
+}
+
+/** [VolumePanelRadioButtonBar] content scope. Use [item] to add more items. */
+interface VolumePanelRadioButtonBarScope {
+
+ /**
+ * Adds a single item to the radio button group.
+ *
+ * @param isSelected true when the item is selected and false the otherwise
+ * @param onItemSelected is called when the item is selected
+ * @param icon of the to show in the indicator bar
+ * @param label to show below the indicator bar for the corresponding [icon]
+ */
+ fun item(
+ isSelected: Boolean,
+ onItemSelected: () -> Unit,
+ icon: @Composable RowScope.() -> Unit = Empty,
+ label: @Composable RowScope.() -> Unit = Empty,
+ )
+}
+
+private val Empty: @Composable RowScope.() -> Unit = {}
+
+private class VolumePanelRadioButtonBarScopeImpl : VolumePanelRadioButtonBarScope {
+
+ var hasSelectedItem: Boolean = false
+ private set
+
+ private val mutableItems: MutableList<Item> = mutableListOf()
+ val items: List<Item> = mutableItems
+
+ override fun item(
+ isSelected: Boolean,
+ onItemSelected: () -> Unit,
+ icon: @Composable RowScope.() -> Unit,
+ label: @Composable RowScope.() -> Unit,
+ ) {
+ require(!isSelected || !hasSelectedItem) { "Only one item should be selected at a time" }
+ hasSelectedItem = hasSelectedItem || isSelected
+ mutableItems.add(
+ Item(
+ isSelected = isSelected,
+ onItemSelected = onItemSelected,
+ icon = icon,
+ label = label,
+ )
+ )
+ }
+}
+
+private class Item(
+ val isSelected: Boolean,
+ val onItemSelected: () -> Unit,
+ val icon: @Composable RowScope.() -> Unit,
+ val label: @Composable RowScope.() -> Unit,
+)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt
new file mode 100644
index 0000000..da29d58
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.spatialaudio
+
+import com.android.systemui.volume.panel.component.button.ui.composable.ButtonComponent
+import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
+import com.android.systemui.volume.panel.component.spatial.domain.SpatialAudioAvailabilityCriteria
+import com.android.systemui.volume.panel.component.spatial.ui.viewmodel.SpatialAudioViewModel
+import com.android.systemui.volume.panel.component.spatialaudio.ui.composable.SpatialAudioPopup
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+
+/** Dagger module, that provides Spatial Audio Volume Panel UI functionality. */
+@Module
+interface SpatialAudioModule {
+
+ @Binds
+ @IntoMap
+ @StringKey(VolumePanelComponents.SPATIAL_AUDIO)
+ fun bindComponentAvailabilityCriteria(
+ criteria: SpatialAudioAvailabilityCriteria
+ ): ComponentAvailabilityCriteria
+
+ companion object {
+
+ @Provides
+ @IntoMap
+ @StringKey(VolumePanelComponents.SPATIAL_AUDIO)
+ fun provideVolumePanelUiComponent(
+ viewModel: SpatialAudioViewModel,
+ popup: SpatialAudioPopup,
+ ): VolumePanelUiComponent = ButtonComponent(viewModel.spatialAudioButton, popup::show)
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
new file mode 100644
index 0000000..bed0ae8
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.spatialaudio.ui.composable
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.common.ui.compose.toColor
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.volume.panel.component.popup.ui.composable.VolumePanelPopup
+import com.android.systemui.volume.panel.component.selector.ui.composable.VolumePanelRadioButtonBar
+import com.android.systemui.volume.panel.component.spatial.ui.viewmodel.SpatialAudioViewModel
+import javax.inject.Inject
+
+class SpatialAudioPopup
+@Inject
+constructor(
+ private val viewModel: SpatialAudioViewModel,
+ private val volumePanelPopup: VolumePanelPopup,
+) {
+
+ /** Shows a popup with the [expandable] animation. */
+ fun show(expandable: Expandable) {
+ volumePanelPopup.show(expandable, { Title() }, { Content(it) })
+ }
+
+ @Composable
+ private fun Title() {
+ Text(
+ text = stringResource(R.string.volume_panel_spatial_audio_title),
+ style = MaterialTheme.typography.titleMedium,
+ textAlign = TextAlign.Center,
+ maxLines = 1,
+ )
+ }
+
+ @Composable
+ private fun Content(dialog: SystemUIDialog) {
+ val isAvailable by viewModel.isAvailable.collectAsState()
+
+ if (!isAvailable) {
+ SideEffect { dialog.dismiss() }
+ return
+ }
+
+ val enabledModelStates by viewModel.spatialAudioButtonByEnabled.collectAsState()
+ if (enabledModelStates.isEmpty()) {
+ return
+ }
+ VolumePanelRadioButtonBar {
+ for (buttonViewModel in enabledModelStates) {
+ item(
+ isSelected = buttonViewModel.button.isChecked,
+ onItemSelected = { viewModel.setEnabled(buttonViewModel.model) },
+ icon = {
+ Icon(
+ icon = buttonViewModel.button.icon,
+ tint = buttonViewModel.iconColor.toColor(),
+ )
+ },
+ label = {
+ Text(
+ text = buttonViewModel.button.label.toString(),
+ style = MaterialTheme.typography.labelMedium,
+ color = buttonViewModel.labelColor.toColor(),
+ )
+ }
+ )
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
index 4d810df..81d2da0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -42,9 +42,6 @@
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.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
@@ -61,12 +58,13 @@
@Composable
fun ColumnVolumeSliders(
viewModels: List<SliderViewModel>,
+ isExpanded: Boolean,
+ onExpandedChanged: (Boolean) -> Unit,
sliderColors: PlatformSliderColors,
isExpandable: Boolean,
modifier: Modifier = Modifier,
) {
require(viewModels.isNotEmpty())
- var isExpanded: Boolean by remember(isExpandable) { mutableStateOf(!isExpandable) }
val transition = updateTransition(isExpanded, label = "CollapsableSliders")
Column(modifier = modifier) {
Row(
@@ -85,7 +83,7 @@
if (isExpandable) {
ExpandButton(
isExpanded = isExpanded,
- onExpandedChanged = { isExpanded = it },
+ onExpandedChanged = onExpandedChanged,
sliderColors,
Modifier,
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index 18a62dc..3e0aee5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -22,6 +22,7 @@
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -30,6 +31,7 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
import com.android.compose.PlatformSlider
import com.android.compose.PlatformSliderColors
import com.android.systemui.common.ui.compose.Icon
@@ -54,7 +56,7 @@
if (isDragging) {
Text(text = value.toInt().toString())
} else {
- state.icon?.let { Icon(icon = it) }
+ state.icon?.let { Icon(modifier = Modifier.size(24.dp), icon = it) }
}
},
colors = sliderColors,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
index 1ca18de..fdf8ee8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
@@ -48,8 +48,11 @@
modifier = modifier.fillMaxWidth(),
)
} else {
+ val isExpanded by viewModel.isExpanded.collectAsState()
ColumnVolumeSliders(
viewModels = sliderViewModels,
+ isExpanded = isExpanded,
+ onExpandedChanged = viewModel::onExpandedChanged,
sliderColors = PlatformSliderDefaults.defaultPlatformSliderColors(),
isExpandable = isPortrait,
modifier = modifier.fillMaxWidth(),
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
index b9b472f..6cff30c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
@@ -21,6 +21,7 @@
import androidx.compose.animation.core.SpringSpec
import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
/**
@@ -147,13 +148,16 @@
}
// Animate the progress to its target value.
- launch { animatable.animateTo(targetProgress, animationSpec) }
- .invokeOnCompletion {
- // Settle the state to Idle(target). Note that this will do nothing if this transition
- // was replaced/interrupted by another one, and this also runs if this coroutine is
- // cancelled, i.e. if [this] coroutine scope is cancelled.
- layoutState.finishTransition(transition, target)
- }
+ transition.job =
+ launch { animatable.animateTo(targetProgress, animationSpec) }
+ .apply {
+ invokeOnCompletion {
+ // Settle the state to Idle(target). Note that this will do nothing if this
+ // transition was replaced/interrupted by another one, and this also runs if
+ // this coroutine is cancelled, i.e. if [this] coroutine scope is cancelled.
+ layoutState.finishTransition(transition, target)
+ }
+ }
return transition
}
@@ -174,8 +178,13 @@
*/
lateinit var animatable: Animatable<Float, AnimationVector1D>
+ /** The job that is animating [animatable]. */
+ lateinit var job: Job
+
override val progress: Float
get() = animatable.value
+
+ override fun finish(): Job = job
}
// TODO(b/290184746): Compute a good default visibility threshold that depends on the layout size
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index b94e49b..63ec54f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -20,6 +20,7 @@
import android.util.Log
import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.SpringSpec
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.runtime.getValue
@@ -30,6 +31,7 @@
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.round
+import com.android.compose.animation.scene.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
@@ -96,9 +98,15 @@
return false
}
+ val swipeTransition = dragController.swipeTransition
+
+ // Don't intercept a transition that is finishing.
+ if (swipeTransition.isFinishing) {
+ return false
+ }
+
// Only intercept the current transition if one of the 2 swipes results is also a transition
// between the same pair of scenes.
- val swipeTransition = dragController.swipeTransition
val fromScene = swipeTransition._currentScene
val swipes = computeSwipes(fromScene, startedPosition, pointersDown = 1)
val (upOrLeft, downOrRight) = swipes.computeSwipesResults(fromScene)
@@ -149,15 +157,24 @@
val fromScene = layoutImpl.scene(transitionState.currentScene)
val swipes = computeSwipes(fromScene, startedPosition, pointersDown)
- val result = swipes.findUserActionResult(fromScene, overSlop, true)
-
- // As we were unable to locate a valid target scene, the initial SwipeTransition cannot be
- // defined. Consequently, a simple NoOp Controller will be returned.
- if (result == null) return NoOpDragController
+ val result =
+ swipes.findUserActionResult(fromScene, overSlop, true)
+ // As we were unable to locate a valid target scene, the initial SwipeTransition
+ // cannot be defined. Consequently, a simple NoOp Controller will be returned.
+ ?: return NoOpDragController
return updateDragController(
swipes = swipes,
- swipeTransition = SwipeTransition(fromScene, result, swipes, layoutImpl, orientation)
+ swipeTransition =
+ SwipeTransition(
+ layoutImpl.state,
+ coroutineScope,
+ fromScene,
+ result,
+ swipes,
+ layoutImpl,
+ orientation,
+ )
)
}
@@ -277,7 +294,7 @@
* @return the consumed delta
*/
override fun onDrag(delta: Float) {
- if (delta == 0f || !isDrivingTransition) return
+ if (delta == 0f || !isDrivingTransition || swipeTransition.isFinishing) return
swipeTransition.dragOffset += delta
val (fromScene, acceleratedOffset) =
@@ -305,6 +322,8 @@
) {
val swipeTransition =
SwipeTransition(
+ layoutState = layoutState,
+ coroutineScope = draggableHandler.coroutineScope,
fromScene = fromScene,
result = result,
swipes = swipes,
@@ -335,10 +354,7 @@
// If the swipe was not committed or if the swipe distance is not computed yet, don't do
// anything.
- if (
- swipeTransition._currentScene != toScene ||
- distance == SwipeTransition.DistanceUnspecified
- ) {
+ if (swipeTransition._currentScene != toScene || distance == DistanceUnspecified) {
return fromScene to 0f
}
@@ -355,15 +371,9 @@
}
}
- private fun snapToScene(scene: SceneKey) {
- if (!isDrivingTransition) return
- swipeTransition.cancelOffsetAnimation()
- layoutState.finishTransition(swipeTransition, idleScene = scene)
- }
-
override fun onStop(velocity: Float, canChangeScene: Boolean) {
// The state was changed since the drag started; don't do anything.
- if (!isDrivingTransition) {
+ if (!isDrivingTransition || swipeTransition.isFinishing) {
return
}
@@ -389,7 +399,7 @@
coroutineScope = draggableHandler.coroutineScope,
initialVelocity = velocity,
targetOffset = targetOffset,
- onAnimationCompleted = { snapToScene(targetScene.key) }
+ targetScene = targetScene.key,
)
}
@@ -406,7 +416,7 @@
var targetScene: Scene
var targetOffset: Float
if (
- distance != SwipeTransition.DistanceUnspecified &&
+ distance != DistanceUnspecified &&
shouldCommitSwipe(
offset,
distance,
@@ -432,8 +442,8 @@
if (targetScene == fromScene) {
0f
} else {
- check(distance != SwipeTransition.DistanceUnspecified) {
- "distance is equal to ${SwipeTransition.DistanceUnspecified}"
+ check(distance != DistanceUnspecified) {
+ "distance is equal to $DistanceUnspecified"
}
distance
}
@@ -451,12 +461,14 @@
val result = swipes.findUserActionResultStrict(velocity)
if (result == null) {
// We will not animate
- snapToScene(fromScene.key)
+ swipeTransition.snapToScene(fromScene.key)
return
}
val newSwipeTransition =
SwipeTransition(
+ layoutState = layoutState,
+ coroutineScope = draggableHandler.coroutineScope,
fromScene = fromScene,
result = result,
swipes = swipes,
@@ -514,6 +526,8 @@
}
private fun SwipeTransition(
+ layoutState: BaseSceneTransitionLayoutState,
+ coroutineScope: CoroutineScope,
fromScene: Scene,
result: UserActionResult,
swipes: Swipes,
@@ -530,6 +544,8 @@
}
return SwipeTransition(
+ layoutState = layoutState,
+ coroutineScope = coroutineScope,
key = result.transitionKey,
_fromScene = fromScene,
_toScene = layoutImpl.scene(result.toScene),
@@ -541,6 +557,8 @@
private fun SwipeTransition(old: SwipeTransition): SwipeTransition {
return SwipeTransition(
+ layoutState = old.layoutState,
+ coroutineScope = old.coroutineScope,
key = old.key,
_fromScene = old._fromScene,
_toScene = old._toScene,
@@ -555,6 +573,8 @@
}
private class SwipeTransition(
+ val layoutState: BaseSceneTransitionLayoutState,
+ val coroutineScope: CoroutineScope,
val key: TransitionKey?,
val _fromScene: Scene,
val _toScene: Scene,
@@ -573,7 +593,7 @@
// Important: If we are going to return early because distance is equal to 0, we should
// still make sure we read the offset before returning so that the calling code still
// subscribes to the offset value.
- val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset
+ val offset = offsetAnimation?.animatable?.value ?: dragOffset
val distance = distance()
if (distance == DistanceUnspecified) {
@@ -588,20 +608,11 @@
/** The current offset caused by the drag gesture. */
var dragOffset by mutableFloatStateOf(0f)
- /**
- * Whether the offset is animated (the user lifted their finger) or if it is driven by gesture.
- */
- var isAnimatingOffset by mutableStateOf(false)
+ /** The offset animation that animates the offset once the user lifts their finger. */
+ private var offsetAnimation: OffsetAnimation? by mutableStateOf(null)
- // If we are not animating offset, it means the offset is being driven by the user's finger.
override val isUserInputOngoing: Boolean
- get() = !isAnimatingOffset
-
- /** The animatable used to animate the offset once the user lifted its finger. */
- val offsetAnimatable = Animatable(0f, OffsetVisibilityThreshold)
-
- /** Job to check that there is at most one offset animation in progress. */
- private var offsetAnimationJob: Job? = null
+ get() = offsetAnimation == null
/**
* The [TransformationSpecImpl] associated to this transition.
@@ -615,8 +626,18 @@
/** The spec to use when animating this transition to either [fromScene] or [toScene]. */
lateinit var swipeSpec: SpringSpec<Float>
+ override val overscrollScope: OverscrollScope =
+ object : OverscrollScope {
+ override val absoluteDistance: Float
+ get() = distance().absoluteValue
+ }
+
private var lastDistance = DistanceUnspecified
+ /** Whether [TransitionState.Transition.finish] was called on this transition. */
+ var isFinishing = false
+ private set
+
/**
* The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is above
* or to the left of [toScene].
@@ -647,25 +668,21 @@
return distance
}
- /** Ends any previous [offsetAnimationJob] and runs the new [job]. */
- private fun startOffsetAnimation(job: () -> Job) {
+ /** Ends any previous [offsetAnimation] and runs the new [animation]. */
+ private fun startOffsetAnimation(animation: () -> OffsetAnimation): OffsetAnimation {
cancelOffsetAnimation()
- offsetAnimationJob = job()
+ return animation().also { offsetAnimation = it }
}
/** Cancel any ongoing offset animation. */
// TODO(b/317063114) This should be a suspended function to avoid multiple jobs running at
// the same time.
fun cancelOffsetAnimation() {
- offsetAnimationJob?.cancel()
- finishOffsetAnimation()
- }
+ val animation = offsetAnimation ?: return
+ offsetAnimation = null
- fun finishOffsetAnimation() {
- if (isAnimatingOffset) {
- isAnimatingOffset = false
- dragOffset = offsetAnimatable.value
- }
+ dragOffset = animation.animatable.value
+ animation.job.cancel()
}
fun animateOffset(
@@ -673,35 +690,73 @@
coroutineScope: CoroutineScope,
initialVelocity: Float,
targetOffset: Float,
- onAnimationCompleted: () -> Unit,
- ) {
- startOffsetAnimation {
- coroutineScope.launch {
- animateOffset(targetOffset, initialVelocity)
- onAnimationCompleted()
+ targetScene: SceneKey,
+ ): OffsetAnimation {
+ return startOffsetAnimation {
+ val animatable = Animatable(dragOffset, OffsetVisibilityThreshold)
+ val job =
+ coroutineScope
+ .launch {
+ animatable.animateTo(
+ targetValue = targetOffset,
+ animationSpec = swipeSpec,
+ initialVelocity = initialVelocity,
+ )
+ }
+ // Make sure that we settle to target scene at the end of the animation or if
+ // the animation is cancelled.
+ .apply { invokeOnCompletion { snapToScene(targetScene) } }
+
+ OffsetAnimation(animatable, job)
+ }
+ }
+
+ fun snapToScene(scene: SceneKey) {
+ if (layoutState.transitionState != this) return
+ cancelOffsetAnimation()
+ layoutState.finishTransition(this, idleScene = scene)
+ }
+
+ override fun finish(): Job {
+ if (isFinishing) return requireNotNull(offsetAnimation).job
+ isFinishing = true
+
+ // If we were already animating the offset, simply return the job.
+ offsetAnimation?.let {
+ return it.job
+ }
+
+ // Animate to the current scene.
+ val targetScene = currentScene
+ val targetOffset =
+ if (targetScene == fromScene) {
+ 0f
+ } else {
+ val distance = distance()
+ check(distance != DistanceUnspecified) {
+ "targetScene != fromScene but distance is unspecified"
+ }
+ distance
}
- }
+
+ val animation =
+ animateOffset(
+ coroutineScope = coroutineScope,
+ initialVelocity = 0f,
+ targetOffset = targetOffset,
+ targetScene = currentScene,
+ )
+ check(offsetAnimation == animation)
+ return animation.job
}
- private suspend fun animateOffset(targetOffset: Float, initialVelocity: Float) {
- if (!isAnimatingOffset) {
- offsetAnimatable.snapTo(dragOffset)
- }
- isAnimatingOffset = true
+ internal class OffsetAnimation(
+ /** The animatable used to animate the offset. */
+ val animatable: Animatable<Float, AnimationVector1D>,
- val animationSpec = transformationSpec
- offsetAnimatable.animateTo(
- targetValue = targetOffset,
- animationSpec = swipeSpec,
- initialVelocity = initialVelocity,
- )
-
- finishOffsetAnimation()
- }
-
- companion object {
- const val DistanceUnspecified = 0f
- }
+ /** The job in which [animatable] is animated. */
+ val job: Job,
+ )
}
private object DefaultSwipeDistance : UserActionDistance {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index cdc4778..be066fd 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -133,18 +133,14 @@
if (shouldComposeMovableElement) {
val movableContent: MovableElementContent =
layoutImpl.movableContents[element]
- ?: movableContentOf {
- contentScope: MovableElementContentScope,
- content: @Composable MovableElementContentScope.() -> Unit ->
- contentScope.content()
- }
+ ?: movableContentOf { content: @Composable () -> Unit -> content() }
.also { layoutImpl.movableContents[element] = it }
// Important: Don't introduce any parent Box or other layout here, because contentScope
// delegates its BoxScope implementation to the Box where this content() function is
// called, so it's important that this movableContent is composed directly under that
// Box.
- movableContent(contentScope, content)
+ movableContent { contentScope.content() }
} else {
// If we are not composed, we still need to lay out an empty space with the same *target
// size* as its movable content, i.e. the same *size when idle*. During transitions,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 1670e9c..25b0895 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -38,15 +38,8 @@
import com.android.compose.ui.util.lerp
import kotlinx.coroutines.CoroutineScope
-/**
- * The type for the content of movable elements.
- *
- * TODO(b/317972419): Revert back to make this movable content have a single @Composable lambda
- * parameter.
- */
-internal typealias MovableElementContent =
- @Composable
- (MovableElementContentScope, @Composable MovableElementContentScope.() -> Unit) -> Unit
+/** The type for the content of movable elements. */
+internal typealias MovableElementContent = @Composable (@Composable () -> Unit) -> Unit
@Stable
internal class SceneTransitionLayoutImpl(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 0fa19bb..e6f5d58 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -31,6 +31,7 @@
import com.android.compose.animation.scene.transition.link.StateLink
import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
/**
@@ -188,13 +189,8 @@
val fromScene: SceneKey,
/** The scene this transition is going to. Can't be the same as fromScene */
- val toScene: SceneKey
+ val toScene: SceneKey,
) : TransitionState {
-
- init {
- check(fromScene != toScene)
- }
-
/**
* The progress of the transition. This is usually in the `[0; 1]` range, but it can also be
* less than `0` or greater than `1` when using transitions with a spring AnimationSpec or
@@ -208,6 +204,21 @@
/** Whether user input is currently driving the transition. */
abstract val isUserInputOngoing: Boolean
+ init {
+ check(fromScene != toScene)
+ }
+
+ /**
+ * Force this transition to finish and animate to [currentScene], so that this transition
+ * progress will settle to either 0% (if [currentScene] == [fromScene]) or 100% (if
+ * [currentScene] == [toScene]) in a finite amount of time.
+ *
+ * @return the [Job] that animates the progress to [currentScene]. It can be used to wait
+ * until the animation is complete or cancel it to snap to [currentScene]. Calling
+ * [finish] multiple times will return the same [Job].
+ */
+ abstract fun finish(): Job
+
/**
* Whether we are transitioning. If [from] or [to] is empty, we will also check that they
* match the scenes we are animating from and/or to.
@@ -225,19 +236,28 @@
interface HasOverscrollProperties {
/**
- * The position of the [TransitionState.Transition.toScene].
+ * The position of the [Transition.toScene].
*
* Used to understand the direction of the overscroll.
*/
val isUpOrLeft: Boolean
/**
- * The relative orientation between [TransitionState.Transition.fromScene] and
- * [TransitionState.Transition.toScene].
+ * The relative orientation between [Transition.fromScene] and [Transition.toScene].
*
* Used to understand the orientation of the overscroll.
*/
val orientation: Orientation
+
+ /**
+ * Scope which can be used in the Overscroll DSL to define a transformation based on the
+ * distance between [Transition.fromScene] and [Transition.toScene].
+ */
+ val overscrollScope: OverscrollScope
+
+ companion object {
+ const val DistanceUnspecified = 0f
+ }
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index 2dd41cd..b466143 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -30,6 +30,7 @@
import com.android.compose.animation.scene.transformation.DrawScale
import com.android.compose.animation.scene.transformation.EdgeTranslate
import com.android.compose.animation.scene.transformation.Fade
+import com.android.compose.animation.scene.transformation.OverscrollTranslate
import com.android.compose.animation.scene.transformation.PropertyTransformation
import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
import com.android.compose.animation.scene.transformation.ScaleSize
@@ -124,7 +125,7 @@
overscrollSpecs.fastForEach { spec ->
if (spec.orientation == orientation && filter(spec)) {
if (match != null) {
- error("Found multiple transition specs for transition $scene")
+ error("Found multiple overscroll specs for overscroll $scene")
}
match = spec
}
@@ -297,6 +298,7 @@
) {
when (current) {
is Translate,
+ is OverscrollTranslate,
is EdgeTranslate,
is AnchoredTranslate -> {
throwIfNotNull(offset, element, name = "offset")
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index bc52a28..2c109a3 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -22,6 +22,7 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
/** Define the [transitions][SceneTransitions] to be used with a [SceneTransitionLayout]. */
fun transitions(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions {
@@ -88,8 +89,7 @@
): OverscrollSpec
}
-@TransitionDsl
-interface OverscrollBuilder : PropertyTransformationBuilder {
+interface BaseTransitionBuilder : PropertyTransformationBuilder {
/**
* The distance it takes for this transition to animate from 0% to 100% when it is driven by a
* [UserAction].
@@ -120,7 +120,7 @@
}
@TransitionDsl
-interface TransitionBuilder : OverscrollBuilder, PropertyTransformationBuilder {
+interface TransitionBuilder : BaseTransitionBuilder {
/**
* The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when
* the transition is triggered (i.e. it is not gesture-based).
@@ -176,6 +176,24 @@
fun reversed(builder: TransitionBuilder.() -> Unit)
}
+@TransitionDsl
+interface OverscrollBuilder : BaseTransitionBuilder {
+ /** Translate the element(s) matching [matcher] by ([x], [y]) pixels. */
+ fun translate(
+ matcher: ElementMatcher,
+ x: OverscrollScope.() -> Float = { 0f },
+ y: OverscrollScope.() -> Float = { 0f },
+ )
+}
+
+interface OverscrollScope {
+ /**
+ * Return the absolute distance between fromScene and toScene, if available, otherwise
+ * [DistanceUnspecified].
+ */
+ val absoluteDistance: Float
+}
+
/**
* An interface to decide where we should draw shared Elements or compose MovableElements.
*
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 65e8ea5..1c9080f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -31,6 +31,7 @@
import com.android.compose.animation.scene.transformation.DrawScale
import com.android.compose.animation.scene.transformation.EdgeTranslate
import com.android.compose.animation.scene.transformation.Fade
+import com.android.compose.animation.scene.transformation.OverscrollTranslate
import com.android.compose.animation.scene.transformation.PropertyTransformation
import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
import com.android.compose.animation.scene.transformation.ScaleSize
@@ -114,7 +115,7 @@
}
}
-internal open class OverscrollBuilderImpl : OverscrollBuilder {
+internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder {
val transformations = mutableListOf<Transformation>()
private var range: TransformationRange? = null
protected var reversed = false
@@ -130,7 +131,7 @@
range = null
}
- private fun transformation(transformation: PropertyTransformation<*>) {
+ protected fun transformation(transformation: PropertyTransformation<*>) {
val transformation =
if (range != null) {
RangedPropertyTransformation(transformation, range!!)
@@ -185,7 +186,7 @@
}
}
-internal class TransitionBuilderImpl : OverscrollBuilderImpl(), TransitionBuilder {
+internal class TransitionBuilderImpl : BaseTransitionBuilderImpl(), TransitionBuilder {
override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
override var swipeSpec: SpringSpec<Float>? = null
override var distance: UserActionDistance? = null
@@ -226,3 +227,13 @@
fractionRange(start, end, builder)
}
}
+
+internal open class OverscrollBuilderImpl : BaseTransitionBuilderImpl(), OverscrollBuilder {
+ override fun translate(
+ matcher: ElementMatcher,
+ x: OverscrollScope.() -> Float,
+ y: OverscrollScope.() -> Float
+ ) {
+ transformation(OverscrollTranslate(matcher, x, y))
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
index 04d5914..849c9d7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
@@ -21,11 +21,11 @@
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.ElementMatcher
+import com.android.compose.animation.scene.OverscrollScope
import com.android.compose.animation.scene.Scene
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
import com.android.compose.animation.scene.TransitionState
-/** Translate an element by a fixed amount of density-independent pixels. */
internal class Translate(
override val matcher: ElementMatcher,
private val x: Dp = 0.dp,
@@ -47,3 +47,28 @@
}
}
}
+
+internal class OverscrollTranslate(
+ override val matcher: ElementMatcher,
+ val x: OverscrollScope.() -> Float = { 0f },
+ val y: OverscrollScope.() -> Float = { 0f },
+) : PropertyTransformation<Offset> {
+ override fun transform(
+ layoutImpl: SceneTransitionLayoutImpl,
+ scene: Scene,
+ element: Element,
+ sceneState: Element.SceneState,
+ transition: TransitionState.Transition,
+ value: Offset,
+ ): Offset {
+ // As this object is created by OverscrollBuilderImpl and we retrieve the current
+ // OverscrollSpec only when the transition implements HasOverscrollProperties, we can assume
+ // that this method was invoked after performing this check.
+ val overscrollProperties = transition as TransitionState.HasOverscrollProperties
+
+ return Offset(
+ x = value.x + overscrollProperties.overscrollScope.x(),
+ y = value.y + overscrollProperties.overscrollScope.y(),
+ )
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
index 33b57b2..73393a1 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt
@@ -18,6 +18,7 @@
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionState
+import kotlinx.coroutines.Job
/** A linked transition which is driven by a [originalTransition]. */
internal class LinkedTransition(
@@ -43,4 +44,6 @@
override val progress: Float
get() = originalTransition.progress
+
+ override fun finish(): Job = originalTransition.finish()
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index eb9b428..1e9a7e2 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -39,6 +39,7 @@
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.launch
import org.junit.Test
import org.junit.runner.RunWith
@@ -892,6 +893,50 @@
}
@Test
+ fun finish() = runGestureTest {
+ // Start at scene C.
+ navigateToSceneC()
+
+ // Swipe up from the middle to transition to scene B.
+ val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+ onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+ assertTransition(fromScene = SceneC, toScene = SceneB, isUserInputOngoing = true)
+
+ // The current transition can be intercepted.
+ assertThat(draggableHandler.shouldImmediatelyIntercept(middle)).isTrue()
+
+ // Finish the transition.
+ val transition = transitionState as Transition
+ val job = transition.finish()
+ assertTransition(isUserInputOngoing = false)
+
+ // The current transition can not be intercepted anymore.
+ assertThat(draggableHandler.shouldImmediatelyIntercept(middle)).isFalse()
+
+ // Calling finish() multiple times returns the same Job.
+ assertThat(transition.finish()).isSameInstanceAs(job)
+ assertThat(transition.finish()).isSameInstanceAs(job)
+ assertThat(transition.finish()).isSameInstanceAs(job)
+
+ // We can join the job to wait for the animation to end.
+ assertTransition()
+ job.join()
+ assertIdle(SceneC)
+ }
+
+ @Test
+ fun finish_cancelled() = runGestureTest {
+ // Swipe up from the middle to transition to scene B.
+ val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+ onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+ assertTransition(fromScene = SceneA, toScene = SceneB)
+
+ // Finish the transition and cancel the returned job.
+ (transitionState as Transition).finish().cancelAndJoin()
+ assertIdle(SceneA)
+ }
+
+ @Test
fun blockTransition() = runGestureTest {
assertIdle(SceneA)
@@ -951,4 +996,15 @@
assertThat(transition).isNotNull()
assertThat(transition!!.progress).isEqualTo(-0.1f)
}
+
+ @Test
+ fun transitionIsImmediatelyUpdatedWhenReleasingFinger() = runGestureTest {
+ // Swipe up from the middle to transition to scene B.
+ val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+ val dragController = onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+ assertTransition(fromScene = SceneA, toScene = SceneB, isUserInputOngoing = true)
+
+ dragController.onDragStopped(velocity = 0f)
+ assertTransition(isUserInputOngoing = false)
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 059a10e..26e01fe 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -539,24 +539,20 @@
}
}
- @Test
- fun elementTransitionDuringOverscroll() {
+ private fun setupOverscrollScenario(
+ layoutWidth: Dp,
+ layoutHeight: Dp,
+ sceneTransitions: SceneTransitionsBuilder.() -> Unit,
+ firstScroll: Float
+ ): MutableSceneTransitionLayoutStateImpl {
// The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
// detected as a drag event.
var touchSlop = 0f
- val overscrollTranslateY = 10.dp
- val layoutWidth = 200.dp
- val layoutHeight = 400.dp
val state =
MutableSceneTransitionLayoutState(
initialScene = TestScenes.SceneA,
- transitions =
- transitions {
- overscroll(TestScenes.SceneB, Orientation.Vertical) {
- translate(TestElements.Foo, y = overscrollTranslateY)
- }
- }
+ transitions = transitions(sceneTransitions),
)
as MutableSceneTransitionLayoutStateImpl
@@ -585,9 +581,30 @@
rule.onRoot().performTouchInput {
val middleTop = Offset((layoutWidth / 2).toPx(), 0f)
down(middleTop)
- // Scroll 50%
- moveBy(Offset(0f, touchSlop + layoutHeight.toPx() * 0.5f), delayMillis = 1_000)
+ val firstScrollHeight = layoutHeight.toPx() * firstScroll
+ moveBy(Offset(0f, touchSlop + firstScrollHeight), delayMillis = 1_000)
}
+ return state
+ }
+
+ @Test
+ fun elementTransitionDuringOverscroll() {
+ val layoutWidth = 200.dp
+ val layoutHeight = 400.dp
+ val overscrollTranslateY = 10.dp
+
+ val state =
+ setupOverscrollScenario(
+ layoutWidth = layoutWidth,
+ layoutHeight = layoutHeight,
+ sceneTransitions = {
+ overscroll(TestScenes.SceneB, Orientation.Vertical) {
+ // On overscroll 100% -> Foo should translate by overscrollTranslateY
+ translate(TestElements.Foo, y = overscrollTranslateY)
+ }
+ },
+ firstScroll = 0.5f, // Scroll 50%
+ )
val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true)
fooElement.assertTopPositionInRootIsEqualTo(0.dp)
@@ -691,4 +708,48 @@
assertThat(state.currentOverscrollSpec).isNotNull()
fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 1.5f)
}
+
+ @Test
+ fun elementTransitionWithDistanceDuringOverscroll() {
+ val layoutWidth = 200.dp
+ val layoutHeight = 400.dp
+ val state =
+ setupOverscrollScenario(
+ layoutWidth = layoutWidth,
+ layoutHeight = layoutHeight,
+ sceneTransitions = {
+ overscroll(TestScenes.SceneB, Orientation.Vertical) {
+ // On overscroll 100% -> Foo should translate by layoutHeight
+ translate(TestElements.Foo, y = { absoluteDistance })
+ }
+ },
+ firstScroll = 1f, // 100% scroll
+ )
+
+ val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true)
+ fooElement.assertTopPositionInRootIsEqualTo(0.dp)
+
+ rule.onRoot().performTouchInput {
+ // Scroll another 50%
+ moveBy(Offset(0f, layoutHeight.toPx() * 0.5f), delayMillis = 1_000)
+ }
+
+ val transition = state.currentTransition
+ assertThat(transition).isNotNull()
+
+ // Scroll 150% (100% scroll + 50% overscroll)
+ assertThat(transition!!.progress).isEqualTo(1.5f)
+ assertThat(state.currentOverscrollSpec).isNotNull()
+ fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 0.5f)
+
+ rule.onRoot().performTouchInput {
+ // Scroll another 100%
+ moveBy(Offset(0f, layoutHeight.toPx()), delayMillis = 1_000)
+ }
+
+ // Scroll 250% (100% scroll + 150% overscroll)
+ assertThat(transition.progress).isEqualTo(2.5f)
+ assertThat(state.currentOverscrollSpec).isNotNull()
+ fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 1.5f)
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
index 35cb691..224ffe2 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
@@ -45,7 +45,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.test.assertSizeIsEqualTo
import com.google.common.truth.Truth.assertThat
-import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -262,7 +261,6 @@
}
@Test
- @Ignore("b/317972419#comment2")
fun movableElementContentIsRecomposedIfContentParametersChange() {
@Composable
fun SceneScope.MovableFoo(text: String, modifier: Modifier = Modifier) {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index 3cbcd73..9baabc3 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -28,6 +28,8 @@
import com.android.compose.test.runMonotonicClockTest
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.job
import kotlinx.coroutines.launch
import org.junit.Rule
import org.junit.Test
@@ -37,16 +39,6 @@
class SceneTransitionLayoutStateTest {
@get:Rule val rule = createComposeRule()
- class TestableTransition(
- fromScene: SceneKey,
- toScene: SceneKey,
- ) : TransitionState.Transition(fromScene, toScene) {
- override var currentScene: SceneKey = fromScene
- override var progress: Float = 0.0f
- override var isInitiatedByUserInput: Boolean = false
- override var isUserInputOngoing: Boolean = false
- }
-
@Test
fun isTransitioningTo_idle() {
val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
@@ -83,25 +75,31 @@
assertThat(transition).isNotNull()
assertThat(state.transitionState).isEqualTo(transition)
- testScheduler.advanceUntilIdle()
+ transition!!.finish().join()
assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
}
@Test
fun setTargetScene_transitionToSameScene() = runMonotonicClockTest {
val state = MutableSceneTransitionLayoutState(SceneA)
- assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
+
+ val transition = state.setTargetScene(SceneB, coroutineScope = this)
+ assertThat(transition).isNotNull()
assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNull()
- testScheduler.advanceUntilIdle()
+
+ transition!!.finish().join()
assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
}
@Test
fun setTargetScene_transitionToDifferentScene() = runMonotonicClockTest {
val state = MutableSceneTransitionLayoutState(SceneA)
+
assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull()
- assertThat(state.setTargetScene(SceneC, coroutineScope = this)).isNotNull()
- testScheduler.advanceUntilIdle()
+ val transition = state.setTargetScene(SceneC, coroutineScope = this)
+ assertThat(transition).isNotNull()
+
+ transition!!.finish().join()
assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneC))
}
@@ -127,11 +125,22 @@
assertThat(state.transitionState).isEqualTo(transition)
// Cancelling the scope/job still sets the state to Idle(targetScene).
- job.cancel()
- testScheduler.advanceUntilIdle()
+ job.cancelAndJoin()
assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
}
+ @Test
+ fun transition_finishReturnsTheSameJobWhenCalledMultipleTimes() = runMonotonicClockTest {
+ val state = MutableSceneTransitionLayoutState(SceneA)
+ val transition = state.setTargetScene(SceneB, coroutineScope = this)
+ assertThat(transition).isNotNull()
+
+ val job = transition!!.finish()
+ assertThat(transition.finish()).isSameInstanceAs(job)
+ assertThat(transition.finish()).isSameInstanceAs(job)
+ assertThat(transition.finish()).isSameInstanceAs(job)
+ }
+
private fun setupLinkedStates(
parentInitialScene: SceneKey = SceneC,
childInitialScene: SceneKey = SceneA,
@@ -159,7 +168,7 @@
fun linkedTransition_startsLinkAndFinishesLinkInToState() {
val (parentState, childState) = setupLinkedStates()
- val childTransition = TestableTransition(SceneA, SceneB)
+ val childTransition = transition(SceneA, SceneB)
childState.startTransition(childTransition, null)
assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
@@ -195,7 +204,7 @@
MutableSceneTransitionLayoutState(SceneA, stateLinks = link)
as BaseSceneTransitionLayoutState
- val childTransition = TestableTransition(SceneA, SceneB)
+ val childTransition = transition(SceneA, SceneB)
childState.startTransition(childTransition, null)
assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
@@ -212,12 +221,13 @@
fun linkedTransition_linkProgressIsEqual() {
val (parentState, childState) = setupLinkedStates()
- val childTransition = TestableTransition(SceneA, SceneB)
+ var progress = 0f
+ val childTransition = transition(SceneA, SceneB, progress = { progress })
childState.startTransition(childTransition, null)
assertThat(parentState.currentTransition?.progress).isEqualTo(0f)
- childTransition.progress = .5f
+ progress = .5f
assertThat(parentState.currentTransition?.progress).isEqualTo(.5f)
}
@@ -225,7 +235,7 @@
fun linkedTransition_reverseTransitionIsNotLinked() {
val (parentState, childState) = setupLinkedStates()
- val childTransition = TestableTransition(SceneB, SceneA)
+ val childTransition = transition(SceneB, SceneA)
childState.startTransition(childTransition, null)
assertThat(childState.isTransitioning(SceneB, SceneA)).isTrue()
@@ -240,7 +250,7 @@
fun linkedTransition_startsLinkAndFinishesLinkInFromState() {
val (parentState, childState) = setupLinkedStates()
- val childTransition = TestableTransition(SceneA, SceneB)
+ val childTransition = transition(SceneA, SceneB)
childState.startTransition(childTransition, null)
childState.finishTransition(childTransition, SceneA)
@@ -252,7 +262,7 @@
fun linkedTransition_startsLinkAndFinishesLinkInUnknownState() {
val (parentState, childState) = setupLinkedStates()
- val childTransition = TestableTransition(SceneA, SceneB)
+ val childTransition = transition(SceneA, SceneB)
childState.startTransition(childTransition, null)
childState.finishTransition(childTransition, SceneD)
@@ -264,8 +274,8 @@
fun linkedTransition_startsLinkButLinkedStateIsTakenOver() {
val (parentState, childState) = setupLinkedStates()
- val childTransition = TestableTransition(SceneA, SceneB)
- val parentTransition = TestableTransition(SceneC, SceneA)
+ val childTransition = transition(SceneA, SceneB)
+ val parentTransition = transition(SceneC, SceneA)
childState.startTransition(childTransition, null)
parentState.startTransition(parentTransition, null)
@@ -315,9 +325,9 @@
@Test
fun snapToIdleIfClose_snapToStart() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
+ val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
state.startTransition(
- transition(from = TestScenes.SceneA, to = TestScenes.SceneB, progress = { 0.2f }),
+ transition(from = SceneA, to = TestScenes.SceneB, progress = { 0.2f }),
transitionKey = null
)
assertThat(state.isTransitioning()).isTrue()
@@ -329,14 +339,14 @@
// Go to the initial scene if it is close to 0.
assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
assertThat(state.isTransitioning()).isFalse()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneA))
+ assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA))
}
@Test
fun snapToIdleIfClose_snapToEnd() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
+ val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
state.startTransition(
- transition(from = TestScenes.SceneA, to = TestScenes.SceneB, progress = { 0.8f }),
+ transition(from = SceneA, to = TestScenes.SceneB, progress = { 0.8f }),
transitionKey = null
)
assertThat(state.isTransitioning()).isTrue()
@@ -354,7 +364,7 @@
@Test
fun linkedTransition_fuzzyLinksAreMatchedAndStarted() {
val (parentState, childState) = setupLinkedStates(SceneC, SceneA, null, null, null, SceneD)
- val childTransition = TestableTransition(SceneA, SceneB)
+ val childTransition = transition(SceneA, SceneB)
childState.startTransition(childTransition, null)
assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
@@ -370,7 +380,7 @@
val (parentState, childState) =
setupLinkedStates(SceneC, SceneA, SceneA, null, null, SceneD)
- val childTransition = TestableTransition(SceneA, SceneB)
+ val childTransition = transition(SceneA, SceneB)
childState.startTransition(childTransition, null)
assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
@@ -385,7 +395,7 @@
fun linkedTransition_fuzzyLinksAreNotMatched() {
val (parentState, childState) =
setupLinkedStates(SceneC, SceneA, SceneB, null, SceneC, SceneD)
- val childTransition = TestableTransition(SceneA, SceneB)
+ val childTransition = transition(SceneA, SceneB)
childState.startTransition(childTransition, null)
assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
@@ -402,18 +412,12 @@
sceneTransitions,
)
state.startTransition(
- object :
- TransitionState.Transition(SceneA, SceneB),
- TransitionState.HasOverscrollProperties {
- override val currentScene: SceneKey = SceneA
- override val progress: Float
- get() = progress()
-
- override val isInitiatedByUserInput: Boolean = false
- override val isUserInputOngoing: Boolean = false
- override val isUpOrLeft: Boolean = false
- override val orientation: Orientation = Orientation.Vertical
- },
+ transition(
+ from = SceneA,
+ to = SceneB,
+ progress = progress,
+ orientation = Orientation.Vertical,
+ ),
transitionKey = null
)
assertThat(state.isTransitioning()).isTrue()
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index c9c3ecc..825fe13 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -22,9 +22,9 @@
import androidx.compose.animation.core.tween
import androidx.compose.foundation.gestures.Orientation
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.transformation.OverscrollTranslate
import com.android.compose.animation.scene.transformation.Transformation
import com.android.compose.animation.scene.transformation.TransformationRange
-import com.android.compose.animation.scene.transformation.Translate
import com.google.common.truth.Correspondence
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -228,12 +228,14 @@
@Test
fun overscrollSpec() {
val transitions = transitions {
- overscroll(TestScenes.SceneA, Orientation.Vertical) { translate(TestElements.Bar) }
+ overscroll(TestScenes.SceneA, Orientation.Vertical) {
+ translate(TestElements.Bar, x = { 1f }, y = { 2f })
+ }
}
val overscrollSpec = transitions.overscrollSpecs.single()
val transformation = overscrollSpec.transformationSpec.transformations.single()
- assertThat(transformation).isInstanceOf(Translate::class.java)
+ assertThat(transformation).isInstanceOf(OverscrollTranslate::class.java)
}
companion object {
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt
index 238b21e1..73a66c6 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt
@@ -16,6 +16,9 @@
package com.android.compose.animation.scene
+import androidx.compose.foundation.gestures.Orientation
+import kotlinx.coroutines.Job
+
/** A utility to easily create a [TransitionState.Transition] in tests. */
fun transition(
from: SceneKey,
@@ -23,11 +26,25 @@
progress: () -> Float = { 0f },
isInitiatedByUserInput: Boolean = false,
isUserInputOngoing: Boolean = false,
+ isUpOrLeft: Boolean = false,
+ orientation: Orientation = Orientation.Horizontal,
): TransitionState.Transition {
- return object : TransitionState.Transition(from, to) {
+ return object : TransitionState.Transition(from, to), TransitionState.HasOverscrollProperties {
override val currentScene: SceneKey = from
- override val progress: Float = progress()
+ override val progress: Float
+ get() = progress()
+
override val isInitiatedByUserInput: Boolean = isInitiatedByUserInput
override val isUserInputOngoing: Boolean = isUserInputOngoing
+ override val isUpOrLeft: Boolean = isUpOrLeft
+ override val orientation: Orientation = orientation
+ override val overscrollScope: OverscrollScope =
+ object : OverscrollScope {
+ override val absoluteDistance = 0f
+ }
+
+ override fun finish(): Job {
+ error("finish() is not supported in test transitions")
+ }
}
}
diff --git a/packages/SystemUI/customization/res/values/ids.xml b/packages/SystemUI/customization/res/values/ids.xml
new file mode 100644
index 0000000..5eafbfc
--- /dev/null
+++ b/packages/SystemUI/customization/res/values/ids.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- View ids for elements in large weather clock -->
+ <item type="id" name="weather_clock_time" />
+ <item type="id" name="weather_clock_date" />
+ <item type="id" name="weather_clock_weather_icon" />
+ <item type="id" name="weather_clock_temperature" />
+ <item type="id" name="weather_clock_alarm_dnd" />
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorTest.kt
index 97c407c..970ce1f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorTest.kt
@@ -47,6 +47,17 @@
private val displayRepository by lazy { kosmos.displayRepository }
@Test
+ fun propertiesInitialized() =
+ testScope.runTest {
+ val propertiesInitialized by collectLastValue(underTest.propertiesInitialized)
+ assertThat(propertiesInitialized).isFalse()
+
+ repository.supportsUdfps()
+ runCurrent()
+ assertThat(propertiesInitialized).isTrue()
+ }
+
+ @Test
fun sensorLocation_resolution1f() =
testScope.runTest {
val currSensorLocation by collectLastValue(underTest.sensorLocation)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
index 09fdd11..bd1403a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
@@ -36,6 +36,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.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -201,6 +202,7 @@
@Test
fun verifySimPin() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(false)
whenever(telephonyManager.createForSubscriptionId(anyInt()))
@@ -208,8 +210,7 @@
whenever(telephonyManager.supplyIccLockPin(anyString()))
.thenReturn(PinResult(PinResult.PIN_RESULT_TYPE_SUCCESS, 1))
- val msg: String? = underTest.verifySim(listOf(0, 0, 0, 0))
- runCurrent()
+ verifySim(listOf(0, 0, 0, 0))
assertThat(msg).isNull()
verify(keyguardUpdateMonitor).reportSimUnlocked(1)
@@ -218,6 +219,7 @@
@Test
fun verifySimPin_incorrect_oneRemainingAttempt() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(false)
whenever(telephonyManager.createForSubscriptionId(anyInt()))
@@ -229,9 +231,7 @@
1,
)
)
-
- val msg: String? = underTest.verifySim(listOf(0, 0, 0, 0))
- runCurrent()
+ verifySim(listOf(0, 0, 0, 0))
assertThat(msg).isNull()
val errorDialogMessage by collectLastValue(bouncerSimRepository.errorDialogMessage)
@@ -245,6 +245,7 @@
@Test
fun verifySimPin_incorrect_threeRemainingAttempts() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(false)
whenever(telephonyManager.createForSubscriptionId(anyInt()))
@@ -257,8 +258,7 @@
)
)
- val msg = underTest.verifySim(listOf(0, 0, 0, 0))
- runCurrent()
+ verifySim(listOf(0, 0, 0, 0))
assertThat(msg).isEqualTo("Enter SIM PIN. You have 3 remaining attempts.")
}
@@ -266,10 +266,11 @@
@Test
fun verifySimPin_notCorrectLength_tooShort() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(false)
- val msg = underTest.verifySim(listOf(0))
+ verifySim(listOf(0))
assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_pin_hint))
}
@@ -277,10 +278,12 @@
@Test
fun verifySimPin_notCorrectLength_tooLong() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
+
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(false)
- val msg = underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_pin_hint))
}
@@ -288,6 +291,7 @@
@Test
fun verifySimPuk() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
whenever(telephonyManager.createForSubscriptionId(anyInt()))
.thenReturn(telephonyManager)
whenever(telephonyManager.supplyIccLockPuk(anyString(), anyString()))
@@ -295,13 +299,13 @@
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(true)
- var msg = underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
assertThat(msg).isEqualTo(resources.getString(R.string.kg_puk_enter_pin_hint))
- msg = underTest.verifySim(listOf(0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0))
assertThat(msg).isEqualTo(resources.getString(R.string.kg_enter_confirm_pin_hint))
- msg = underTest.verifySim(listOf(0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0))
assertThat(msg).isNull()
runCurrent()
@@ -311,37 +315,49 @@
@Test
fun verifySimPuk_inputTooShort() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
+
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(true)
- val msg = underTest.verifySim(listOf(0, 0, 0, 0))
+
+ verifySim(listOf(0, 0, 0, 0))
assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_puk_hint))
}
@Test
fun verifySimPuk_pinNotCorrectLength() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(true)
- underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
- val msg = underTest.verifySim(listOf(0, 0, 0))
+ verifySim(listOf(0, 0, 0))
+
assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_pin_hint))
}
@Test
fun verifySimPuk_confirmedPinDoesNotMatch() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
+
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(true)
- underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
- underTest.verifySim(listOf(0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0))
- val msg = underTest.verifySim(listOf(0, 0, 0, 1))
+ verifySim(listOf(0, 0, 0, 1))
assertThat(msg).isEqualTo(resources.getString(R.string.kg_puk_enter_pin_hint))
}
+ private suspend fun TestScope.verifySim(pinDigits: List<Int>) {
+ runCurrent()
+ underTest.verifySim(pinDigits)
+ }
+
@Test
fun onErrorDialogDismissed_clearsErrorDialogMessageInRepository() {
bouncerSimRepository.setSimVerificationErrorMessage("abc")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
new file mode 100644
index 0000000..def63ec
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal
+
+import android.service.dream.dreamManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+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.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.power.shared.model.ScreenPowerState
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalDreamStartableTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var underTest: CommunalDreamStartable
+
+ private val dreamManager by lazy { kosmos.dreamManager }
+ private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+ private val powerRepository by lazy { kosmos.fakePowerRepository }
+
+ @Before
+ fun setUp() {
+ underTest =
+ CommunalDreamStartable(
+ powerInteractor = kosmos.powerInteractor,
+ keyguardInteractor = kosmos.keyguardInteractor,
+ keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
+ dreamManager = dreamManager,
+ bgScope = kosmos.applicationCoroutineScope,
+ )
+ .apply { start() }
+ }
+
+ @Test
+ fun startDreamWhenTransitioningToHub() =
+ testScope.runTest {
+ keyguardRepository.setDreaming(false)
+ powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+ whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(true)
+ runCurrent()
+
+ verify(dreamManager, never()).startDream()
+
+ transition(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB)
+
+ verify(dreamManager).startDream()
+ }
+
+ @Test
+ fun shouldNotStartDreamWhenIneligibleToDream() =
+ testScope.runTest {
+ keyguardRepository.setDreaming(false)
+ powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+ // Not eligible to dream
+ whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(false)
+ transition(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB)
+
+ verify(dreamManager, never()).startDream()
+ }
+
+ @Test
+ fun shouldNotStartDreamIfAlreadyDreaming() =
+ testScope.runTest {
+ keyguardRepository.setDreaming(true)
+ powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+ whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(true)
+ transition(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB)
+
+ verify(dreamManager, never()).startDream()
+ }
+
+ @Test
+ fun shouldNotStartDreamForInvalidTransition() =
+ testScope.runTest {
+ keyguardRepository.setDreaming(true)
+ powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+ whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(true)
+
+ // Verify we do not trigger dreaming for any other state besides glanceable hub
+ for (state in KeyguardState.entries) {
+ if (state == KeyguardState.GLANCEABLE_HUB) continue
+ transition(from = KeyguardState.GLANCEABLE_HUB, to = state)
+ verify(dreamManager, never()).startDream()
+ }
+ }
+
+ private suspend fun TestScope.transition(from: KeyguardState, to: KeyguardState) {
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = from,
+ to = to,
+ testScope = this
+ )
+ runCurrent()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index ce6445b..d624bf7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal
+import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -25,13 +26,17 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dock.dockManager
import com.android.systemui.dock.fakeDockManager
+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.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
@@ -54,11 +59,15 @@
@Before
fun setUp() {
with(kosmos) {
+ fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT)
+
underTest =
CommunalSceneStartable(
dockManager = dockManager,
communalInteractor = communalInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
+ keyguardInteractor = keyguardInteractor,
+ systemSettings = fakeSettings,
applicationScope = applicationCoroutineScope,
bgScope = applicationCoroutineScope,
)
@@ -246,6 +255,95 @@
}
}
+ @Test
+ fun hubTimeout_whenDreaming() =
+ with(kosmos) {
+ testScope.runTest {
+ // Device is dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(true)
+ communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+ val scene by collectLastValue(communalInteractor.desiredScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Scene times out back to blank after the screen timeout.
+ advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Blank)
+ }
+ }
+
+ @Test
+ fun hubTimeout_dreamStopped() =
+ with(kosmos) {
+ testScope.runTest {
+ // Device is dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(true)
+ communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+ val scene by collectLastValue(communalInteractor.desiredScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Wait a bit, but not long enough to timeout.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Dream stops, timeout is cancelled and device stays on hub, because the regular
+ // screen timeout will take effect at this point.
+ fakeKeyguardRepository.setDreaming(false)
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ }
+ }
+
+ @Test
+ fun hubTimeout_userActivityTriggered_resetsTimeout() =
+ with(kosmos) {
+ testScope.runTest {
+ // Device is dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(true)
+ communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+ val scene by collectLastValue(communalInteractor.desiredScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Wait a bit, but not long enough to timeout.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+
+ // Send user interaction to reset timeout.
+ communalInteractor.signalUserInteraction()
+
+ // If user activity didn't reset timeout, we would have gone back to Blank by now.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Timeout happens one interval after the user interaction.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Blank)
+ }
+ }
+
+ @Test
+ fun hubTimeout_screenTimeoutChanged() =
+ with(kosmos) {
+ testScope.runTest {
+ fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2)
+
+ // Device is dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(true)
+ communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+ val scene by collectLastValue(communalInteractor.desiredScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Scene times out back to blank after the screen timeout.
+ advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Blank)
+ }
+ }
+
private fun TestScope.updateDocked(docked: Boolean) =
with(kosmos) {
runCurrent()
@@ -260,4 +358,8 @@
setCommunalAvailable(true)
runCurrent()
}
+
+ companion object {
+ private const val SCREEN_TIMEOUT = 1000
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index eafd503..a5707e1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -19,8 +19,12 @@
import android.app.smartspace.SmartspaceTarget
import android.appwidget.AppWidgetProviderInfo
+import android.content.Intent
import android.content.pm.UserInfo
import android.os.UserHandle
+import android.os.UserManager
+import android.os.userManager
+import android.provider.Settings
import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
import android.widget.RemoteViews
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -50,6 +54,8 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.activityStarter
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
@@ -61,6 +67,9 @@
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.fakeSettings
@@ -73,6 +82,8 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
@@ -103,6 +114,8 @@
private lateinit var editWidgetsActivityStarter: EditWidgetsActivityStarter
private lateinit var sceneInteractor: SceneInteractor
private lateinit var userTracker: FakeUserTracker
+ private lateinit var activityStarter: ActivityStarter
+ private lateinit var userManager: UserManager
private lateinit var underTest: CommunalInteractor
@@ -121,9 +134,13 @@
communalPrefsRepository = kosmos.fakeCommunalPrefsRepository
sceneInteractor = kosmos.sceneInteractor
userTracker = kosmos.fakeUserTracker
+ activityStarter = kosmos.activityStarter
+ userManager = kosmos.userManager
whenever(mainUser.isMain).thenReturn(true)
whenever(secondaryUser.isMain).thenReturn(false)
+ whenever(userManager.isQuietModeEnabled(any<UserHandle>())).thenReturn(false)
+ whenever(userManager.isManagedProfile(anyInt())).thenReturn(false)
userRepository.setUserInfos(listOf(mainUser, secondaryUser))
kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
@@ -800,6 +817,16 @@
}
@Test
+ fun navigateToCommunalWidgetSettings_startsActivity() =
+ testScope.runTest {
+ underTest.navigateToCommunalWidgetSettings()
+ val intentCaptor = argumentCaptor<Intent>()
+ verify(activityStarter)
+ .postStartActivityDismissingKeyguard(capture(intentCaptor), eq(0))
+ assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_COMMUNAL_SETTING)
+ }
+
+ @Test
fun filterWidgets_whenUserProfileRemoved() =
testScope.runTest {
// Keyguard showing, and tutorial completed.
@@ -832,6 +859,63 @@
}
@Test
+ fun widgetContent_inQuietMode() =
+ testScope.runTest {
+ // Keyguard showing, and tutorial completed.
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setKeyguardOccluded(false)
+ tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+
+ // Work profile is set up.
+ val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
+ userRepository.setUserInfos(userInfos)
+ userTracker.set(
+ userInfos = userInfos,
+ selectedUserIndex = 0,
+ )
+ runCurrent()
+
+ // Keyguard widgets are allowed.
+ kosmos.fakeSettings.putIntForUser(
+ CommunalSettingsRepositoryImpl.GLANCEABLE_HUB_CONTENT_SETTING,
+ AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
+ mainUser.id
+ )
+ runCurrent()
+
+ // When work profile is paused.
+ whenever(userManager.isQuietModeEnabled(eq(UserHandle.of(USER_INFO_WORK.id))))
+ .thenReturn(true)
+ whenever(userManager.isManagedProfile(eq(USER_INFO_WORK.id))).thenReturn(true)
+
+ val widgetContent by collectLastValue(underTest.widgetContent)
+ val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
+ val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id)
+ val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
+ val widgets = listOf(widget1, widget2, widget3)
+ widgetRepository.setCommunalWidgets(widgets)
+
+ // The work profile widget is in quiet mode, while other widgets are not.
+ assertThat(widgetContent).hasSize(3)
+ widgetContent!!.forEach { model ->
+ assertThat(model)
+ .isInstanceOf(CommunalContentModel.WidgetContent.Widget::class.java)
+ }
+ assertThat(
+ (widgetContent!![0] as CommunalContentModel.WidgetContent.Widget).inQuietMode
+ )
+ .isTrue()
+ assertThat(
+ (widgetContent!![1] as CommunalContentModel.WidgetContent.Widget).inQuietMode
+ )
+ .isFalse()
+ assertThat(
+ (widgetContent!![2] as CommunalContentModel.WidgetContent.Widget).inQuietMode
+ )
+ .isFalse()
+ }
+
+ @Test
fun widgetContent_containsDisabledWidgets_whenCategoryNotAllowed() =
testScope.runTest {
// Communal available, and tutorial completed.
@@ -932,7 +1016,10 @@
private fun createWidgetForUser(appWidgetId: Int, userId: Int): CommunalWidgetContentModel =
mock<CommunalWidgetContentModel> {
whenever(this.appWidgetId).thenReturn(appWidgetId)
- val providerInfo = mock<AppWidgetProviderInfo>()
+ val providerInfo =
+ mock<AppWidgetProviderInfo>().apply {
+ widgetCategory = AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD
+ }
whenever(providerInfo.profile).thenReturn(UserHandle(userId))
whenever(this.providerInfo).thenReturn(providerInfo)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 563aad1..8f802b8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -39,6 +39,7 @@
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -113,6 +114,7 @@
kosmos.communalInteractor,
kosmos.communalTutorialInteractor,
kosmos.shadeInteractor,
+ kosmos.deviceEntryInteractor,
mediaHost,
logcatLogBuffer("CommunalViewModelTest"),
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt
index 2e9ee5c..4a7757b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt
@@ -21,6 +21,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -35,6 +36,7 @@
private val kosmos = testKosmos()
private val biometricSettingsRepository = kosmos.biometricSettingsRepository
private val underTest = kosmos.deviceEntryBiometricSettingsInteractor
+ private val testScope = kosmos.testScope
@Test
fun isCoex_true() = runTest {
@@ -59,4 +61,25 @@
biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
assertThat(isCoex).isFalse()
}
+
+ @Test
+ fun authenticationFlags_providesAuthFlagsFromRepository() =
+ testScope.runTest {
+ assertThat(underTest.authenticationFlags)
+ .isSameInstanceAs(biometricSettingsRepository.authenticationFlags)
+ }
+
+ @Test
+ fun isFaceAuthEnrolledAndEnabled_providesValueFromRepository() =
+ testScope.runTest {
+ assertThat(underTest.isFaceAuthEnrolledAndEnabled)
+ .isSameInstanceAs(biometricSettingsRepository.isFaceAuthEnrolledAndEnabled)
+ }
+
+ @Test
+ fun isFingerprintAuthEnrolledAndEnabled_providesValueFromRepository() =
+ testScope.runTest {
+ assertThat(underTest.isFingerprintAuthEnrolledAndEnabled)
+ .isSameInstanceAs(biometricSettingsRepository.isFingerprintEnrolledAndEnabled)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index 4f44705..70ceb2a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -19,6 +19,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.SceneKey
+import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -27,8 +28,21 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.AdaptiveAuthRequest
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.BouncerLockedOut
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.PolicyLockdown
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.SecurityTimeout
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.TrustAgentDisabled
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UnattendedUpdate
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UserLockdown
+import com.android.systemui.flags.fakeSystemPropertiesHelper
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeTrustRepository
+import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
@@ -36,6 +50,7 @@
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -230,8 +245,8 @@
assertThat(canSwipeToEnter).isFalse()
trustRepository.setCurrentUserTrusted(true)
- runCurrent()
faceAuthRepository.isAuthenticated.value = false
+ runCurrent()
assertThat(canSwipeToEnter).isTrue()
}
@@ -383,6 +398,204 @@
assertThat(isUnlocked).isTrue()
}
+ @Test
+ fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_alwaysNull() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to null,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to null
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenFaceIsEnrolledAndEnabled_mapsToAuthFlagsState() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
+ "not mainline reboot"
+ )
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
+ DeviceNotUnlockedSinceReboot,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
+ AdaptiveAuthRequest,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
+ BouncerLockedOut,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+ SecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+ UserLockdown,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+ NonStrongBiometricsSecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ UnattendedUpdate,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ PolicyLockdown,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ null,
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenFingerprintIsEnrolledAndEnabled_mapsToAuthFlagsState() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
+ "not mainline reboot"
+ )
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
+ DeviceNotUnlockedSinceReboot,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
+ AdaptiveAuthRequest,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
+ BouncerLockedOut,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+ SecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+ UserLockdown,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+ NonStrongBiometricsSecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ UnattendedUpdate,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ PolicyLockdown,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ null,
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenTrustAgentIsEnabled_mapsToAuthFlagsState() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
+ kosmos.fakeTrustRepository.setCurrentUserTrustManaged(false)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
+ "not mainline reboot"
+ )
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
+ DeviceNotUnlockedSinceReboot,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
+ AdaptiveAuthRequest,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
+ BouncerLockedOut,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+ SecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+ UserLockdown,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+ NonStrongBiometricsSecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ UnattendedUpdate,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ PolicyLockdown,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to
+ TrustAgentDisabled,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ TrustAgentDisabled,
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenDeviceRebootedForMainlineUpdate_mapsToTheCorrectReason() =
+ testScope.runTest {
+ val deviceEntryRestrictionReason by
+ collectLastValue(underTest.deviceEntryRestrictionReason)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
+ DeviceEntryInteractor.REBOOT_MAINLINE_UPDATE
+ )
+ kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
+ AuthenticationFlags(
+ userId = 1,
+ flag = LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+ )
+ )
+ runCurrent()
+
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason).isNull()
+
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason)
+ .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
+
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason)
+ .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
+
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason)
+ .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
+ }
+
+ private fun TestScope.verifyRestrictionReasonsForAuthFlags(
+ vararg authFlagToDeviceEntryRestriction: Pair<Int, DeviceEntryRestrictionReason?>
+ ) {
+ val deviceEntryRestrictionReason by collectLastValue(underTest.deviceEntryRestrictionReason)
+
+ authFlagToDeviceEntryRestriction.forEach { (flag, expectedReason) ->
+ kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
+ AuthenticationFlags(userId = 1, flag = flag)
+ )
+ runCurrent()
+
+ if (expectedReason == null) {
+ assertThat(deviceEntryRestrictionReason).isNull()
+ } else {
+ assertThat(deviceEntryRestrictionReason).isEqualTo(expectedReason)
+ }
+ }
+ }
+
private fun switchToScene(sceneKey: SceneKey) {
sceneInteractor.changeScene(sceneKey, "reason")
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index c670506..86fdaa5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -8,7 +8,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.complication.ComplicationHostViewController
-import com.android.systemui.dreams.ui.viewmodel.DreamOverlayViewModel
+import com.android.systemui.dreams.ui.viewmodel.DreamViewModel
import com.android.systemui.log.core.FakeLogBuffer
import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.util.mockito.argumentCaptor
@@ -45,7 +45,7 @@
@Mock private lateinit var hostViewController: ComplicationHostViewController
@Mock private lateinit var statusBarViewController: DreamOverlayStatusBarViewController
@Mock private lateinit var stateController: DreamOverlayStateController
- @Mock private lateinit var transitionViewModel: DreamOverlayViewModel
+ @Mock private lateinit var transitionViewModel: DreamViewModel
private val logBuffer = FakeLogBuffer.Factory.create()
private lateinit var controller: DreamOverlayAnimationsController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
index 0a3aea7..723f6a2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
@@ -27,6 +27,8 @@
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.wakelock.WakeLockFake
+import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -44,6 +46,9 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
+ private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
+ private lateinit var fakeWakeLock: WakeLockFake
+
@Mock private lateinit var taskFragmentComponentFactory: TaskFragmentComponent.Factory
@Mock private lateinit var taskFragmentComponent: TaskFragmentComponent
@Mock private lateinit var activity: Activity
@@ -57,6 +62,10 @@
whenever(taskFragmentComponentFactory.create(any(), any(), any(), any()))
.thenReturn(taskFragmentComponent)
+ fakeWakeLock = WakeLockFake()
+ fakeWakeLockBuilder = WakeLockFake.Builder(context)
+ fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
+
whenever(controlsComponent.getControlsListingController())
.thenReturn(Optional.of(controlsListingController))
@@ -87,12 +96,29 @@
verify(taskFragmentComponentFactory, never()).create(any(), any(), any(), any())
}
+ @Test
+ fun testAttachWindow_wakeLockAcquired() =
+ testScope.runTest {
+ underTest.onAttachedToWindow()
+ assertThat(fakeWakeLock.isHeld).isTrue()
+ }
+ @Test
+ fun testDetachWindow_wakeLockCanBeReleased() =
+ testScope.runTest {
+ underTest.onAttachedToWindow()
+ assertThat(fakeWakeLock.isHeld).isTrue()
+
+ underTest.onDetachedFromWindow()
+ assertThat(fakeWakeLock.isHeld).isFalse()
+ }
+
private fun buildService(activityProvider: DreamActivityProvider): HomeControlsDreamService =
with(kosmos) {
return HomeControlsDreamService(
controlsSettingsRepository = FakeControlsSettingsRepository(),
taskFragmentFactory = taskFragmentComponentFactory,
homeControlsComponentInteractor = homeControlsComponentInteractor,
+ fakeWakeLockBuilder,
dreamActivityProvider = activityProvider,
bgDispatcher = testDispatcher,
logBuffer = logcatLogBuffer("HomeControlsDreamServiceTest")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
index 87b1bbb..1adf414 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
@@ -40,7 +40,6 @@
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import java.util.Optional
@@ -50,7 +49,6 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -136,12 +134,17 @@
@Test
@DisableFlags(FLAG_HOME_PANEL_DREAM)
- fun testStartDoesNotRunDreamServiceWhenFlagIsDisabled() =
+ fun testStartDisablesDreamServiceWhenFlagIsDisabled() =
testScope.runTest {
selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL)
startable.start()
runCurrent()
- verify(packageManager, never()).setComponentEnabledSetting(any(), any(), any())
+ verify(packageManager)
+ .setComponentEnabledSetting(
+ eq(componentName),
+ eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
+ eq(PackageManager.DONT_KILL_APP)
+ )
}
private fun ControlsServiceInfo(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
new file mode 100644
index 0000000..8f03717
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.qs
+
+import android.os.VibrationEffect
+import android.testing.TestableLooper.RunWithLooper
+import android.view.MotionEvent
+import android.view.View
+import androidx.test.core.view.MotionEventBuilder
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.AnimatorTestRule
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWithLooper(setAsMainLooper = true)
+class QSLongPressEffectTest : SysuiTestCase() {
+
+ @Rule @JvmField val mMockitoRule: MockitoRule = MockitoJUnit.rule()
+ @Mock private lateinit var vibratorHelper: VibratorHelper
+ @Mock private lateinit var testView: View
+ @get:Rule val animatorTestRule = AnimatorTestRule(this)
+ private val kosmos = testKosmos()
+
+ private val effectDuration = 400
+ private val lowTickDuration = 12
+ private val spinDuration = 133
+
+ private lateinit var longPressEffect: QSLongPressEffect
+
+ @Before
+ fun setup() {
+ whenever(
+ vibratorHelper.getPrimitiveDurations(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+ VibrationEffect.Composition.PRIMITIVE_SPIN,
+ )
+ )
+ .thenReturn(intArrayOf(lowTickDuration, spinDuration))
+
+ longPressEffect =
+ QSLongPressEffect(
+ vibratorHelper,
+ effectDuration,
+ )
+ }
+
+ @Test
+ fun onActionDown_whileIdle_startsWait() = testWithScope {
+ // GIVEN an action down event occurs
+ val downEvent = buildMotionEvent(MotionEvent.ACTION_DOWN)
+ longPressEffect.onTouch(testView, downEvent)
+
+ // THEN the effect moves to the TIMEOUT_WAIT state
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.TIMEOUT_WAIT)
+ }
+
+ @Test
+ fun onActionCancel_whileWaiting_goesIdle() = testWhileWaiting {
+ // GIVEN an action cancel occurs
+ val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
+ longPressEffect.onTouch(testView, cancelEvent)
+
+ // THEN the effect goes back to idle and does not start
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+ assertEffectDidNotStart()
+ }
+
+ @Test
+ fun onActionUp_whileWaiting_performsClick() = testWhileWaiting {
+ // GIVEN an action is being collected
+ val action by collectLastValue(longPressEffect.actionType)
+
+ // GIVEN an action up occurs
+ val upEvent = buildMotionEvent(MotionEvent.ACTION_UP)
+ longPressEffect.onTouch(testView, upEvent)
+
+ // THEN the action to invoke is the click action and the effect does not start
+ assertThat(action).isEqualTo(QSLongPressEffect.ActionType.CLICK)
+ assertEffectDidNotStart()
+ }
+
+ @Test
+ fun onWaitComplete_whileWaiting_beginsEffect() = testWhileWaiting {
+ // GIVEN the pressed timeout is complete
+ advanceTimeBy(QSLongPressEffect.PRESSED_TIMEOUT + 10L)
+
+ // THEN the effect starts
+ assertEffectStarted()
+ }
+
+ @Test
+ fun onActionUp_whileEffectHasBegun_reversesEffect() = testWhileRunning {
+ // GIVEN that the effect is at the middle of its completion (progress of 50%)
+ animatorTestRule.advanceTimeBy(effectDuration / 2L)
+
+ // WHEN an action up occurs
+ val upEvent = buildMotionEvent(MotionEvent.ACTION_UP)
+ longPressEffect.onTouch(testView, upEvent)
+
+ // THEN the effect gets reversed at 50% progress
+ assertEffectReverses(0.5f)
+ }
+
+ @Test
+ fun onActionCancel_whileEffectHasBegun_reversesEffect() = testWhileRunning {
+ // GIVEN that the effect is at the middle of its completion (progress of 50%)
+ animatorTestRule.advanceTimeBy(effectDuration / 2L)
+
+ // WHEN an action cancel occurs
+ val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
+ longPressEffect.onTouch(testView, cancelEvent)
+
+ // THEN the effect gets reversed at 50% progress
+ assertEffectReverses(0.5f)
+ }
+
+ @Test
+ fun onAnimationComplete_effectEnds() = testWhileRunning {
+ // GIVEN that the animation completes
+ animatorTestRule.advanceTimeBy(effectDuration + 10L)
+
+ // THEN the long-press effect completes
+ assertEffectCompleted()
+ }
+
+ @Test
+ fun onActionDown_whileRunningBackwards_resets() = testWhileRunning {
+ // GIVEN that the effect is at the middle of its completion (progress of 50%)
+ animatorTestRule.advanceTimeBy(effectDuration / 2L)
+
+ // GIVEN an action cancel occurs and the effect gets reversed
+ val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
+ longPressEffect.onTouch(testView, cancelEvent)
+
+ // GIVEN an action down occurs
+ val downEvent = buildMotionEvent(MotionEvent.ACTION_DOWN)
+ longPressEffect.onTouch(testView, downEvent)
+
+ // THEN the effect resets
+ assertEffectResets()
+ }
+
+ @Test
+ fun onAnimationComplete_whileRunningBackwards_goesToIdle() = testWhileRunning {
+ // GIVEN that the effect is at the middle of its completion (progress of 50%)
+ animatorTestRule.advanceTimeBy(effectDuration / 2L)
+
+ // GIVEN an action cancel occurs and the effect gets reversed
+ val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
+ longPressEffect.onTouch(testView, cancelEvent)
+
+ // GIVEN that the animation completes after a sufficient amount of time
+ animatorTestRule.advanceTimeBy(effectDuration.toLong())
+
+ // THEN the state goes to [QSLongPressEffect.State.IDLE]
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+ }
+
+ private fun buildMotionEvent(action: Int): MotionEvent =
+ MotionEventBuilder.newBuilder().setAction(action).build()
+
+ private fun testWithScope(test: suspend TestScope.() -> Unit) =
+ with(kosmos) {
+ testScope.runTest {
+ // GIVEN an effect with a testing scope
+ longPressEffect.scope = CoroutineScope(UnconfinedTestDispatcher(testScheduler))
+
+ // THEN run the test
+ test()
+ }
+ }
+
+ private fun testWhileWaiting(test: suspend TestScope.() -> Unit) =
+ with(kosmos) {
+ testScope.runTest {
+ // GIVEN an effect with a testing scope
+ longPressEffect.scope = CoroutineScope(UnconfinedTestDispatcher(testScheduler))
+
+ // GIVEN the TIMEOUT_WAIT state is entered
+ val downEvent =
+ MotionEventBuilder.newBuilder().setAction(MotionEvent.ACTION_DOWN).build()
+ longPressEffect.onTouch(testView, downEvent)
+
+ // THEN run the test
+ test()
+ }
+ }
+
+ private fun testWhileRunning(test: suspend TestScope.() -> Unit) =
+ with(kosmos) {
+ testScope.runTest {
+ // GIVEN an effect with a testing scope
+ longPressEffect.scope = CoroutineScope(UnconfinedTestDispatcher(testScheduler))
+
+ // GIVEN the down event that enters the TIMEOUT_WAIT state
+ val downEvent =
+ MotionEventBuilder.newBuilder().setAction(MotionEvent.ACTION_DOWN).build()
+ longPressEffect.onTouch(testView, downEvent)
+
+ // GIVEN that the timeout completes and the effect starts
+ advanceTimeBy(QSLongPressEffect.PRESSED_TIMEOUT + 10L)
+
+ // THEN run the test
+ test()
+ }
+ }
+
+ /**
+ * Asserts that the effect started by checking that:
+ * 1. The effect progress is 0f
+ * 2. Initial hint haptics are played
+ * 3. The internal state is [QSLongPressEffect.State.RUNNING_FORWARD]
+ */
+ private fun TestScope.assertEffectStarted() {
+ val effectProgress by collectLastValue(longPressEffect.effectProgress)
+ val longPressHint =
+ LongPressHapticBuilder.createLongPressHint(
+ lowTickDuration,
+ spinDuration,
+ effectDuration,
+ )
+
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.RUNNING_FORWARD)
+ assertThat(effectProgress).isEqualTo(0f)
+ assertThat(longPressHint).isNotNull()
+ verify(vibratorHelper).vibrate(longPressHint!!)
+ }
+
+ /**
+ * Asserts that the effect did not start by checking that:
+ * 1. No effect progress is emitted
+ * 2. No haptics are played
+ * 3. The internal state is not [QSLongPressEffect.State.RUNNING_BACKWARDS] or
+ * [QSLongPressEffect.State.RUNNING_FORWARD]
+ */
+ private fun TestScope.assertEffectDidNotStart() {
+ val effectProgress by collectLastValue(longPressEffect.effectProgress)
+
+ assertThat(longPressEffect.state).isNotEqualTo(QSLongPressEffect.State.RUNNING_FORWARD)
+ assertThat(longPressEffect.state).isNotEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS)
+ assertThat(effectProgress).isNull()
+ verify(vibratorHelper, never()).vibrate(any(/* type= */ VibrationEffect::class.java))
+ }
+
+ /**
+ * Asserts that the effect completes by checking that:
+ * 1. The progress is null
+ * 2. The final snap haptics are played
+ * 3. The internal state goes back to [QSLongPressEffect.State.IDLE]
+ * 4. The action to perform on the tile is the long-press action
+ */
+ private fun TestScope.assertEffectCompleted() {
+ val action by collectLastValue(longPressEffect.actionType)
+ val effectProgress by collectLastValue(longPressEffect.effectProgress)
+ val snapEffect = LongPressHapticBuilder.createSnapEffect()
+
+ assertThat(effectProgress).isNull()
+ assertThat(snapEffect).isNotNull()
+ verify(vibratorHelper).vibrate(snapEffect!!)
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+ assertThat(action).isEqualTo(QSLongPressEffect.ActionType.LONG_PRESS)
+ }
+
+ /**
+ * Assert that the effect gets reverted by checking that:
+ * 1. The internal state is [QSLongPressEffect.State.RUNNING_BACKWARDS]
+ * 2. The reverse haptics plays at the point where the animation was paused
+ */
+ private fun assertEffectReverses(pausedProgress: Float) {
+ val reverseHaptics =
+ LongPressHapticBuilder.createReversedEffect(
+ pausedProgress,
+ lowTickDuration,
+ effectDuration,
+ )
+
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS)
+ assertThat(reverseHaptics).isNotNull()
+ verify(vibratorHelper).vibrate(reverseHaptics!!)
+ }
+
+ /**
+ * Asserts that the effect resets by checking that:
+ * 1. The effect progress resets to 0
+ * 2. The internal state goes back to [QSLongPressEffect.State.TIMEOUT_WAIT]
+ */
+ private fun TestScope.assertEffectResets() {
+ val effectProgress by collectLastValue(longPressEffect.effectProgress)
+ assertThat(effectProgress).isEqualTo(0f)
+
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.TIMEOUT_WAIT)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 128b465..19b80da 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -25,7 +25,6 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
-import com.android.systemui.common.shared.model.Position
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.doze.DozeMachine
import com.android.systemui.doze.DozeTransitionCallback
@@ -152,24 +151,6 @@
}
@Test
- fun clockPosition() =
- testScope.runTest {
- assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0))
-
- underTest.setClockPosition(0, 1)
- assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 1))
-
- underTest.setClockPosition(1, 9)
- assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 9))
-
- underTest.setClockPosition(1, 0)
- assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 0))
-
- underTest.setClockPosition(3, 1)
- assertThat(underTest.clockPosition.value).isEqualTo(Position(3, 1))
- }
-
- @Test
fun dozeTimeTick() =
testScope.runTest {
val lastDozeTimeTick by collectLastValue(underTest.dozeTimeTick)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
index b3380ff..69a1a0f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.data.repository
import android.graphics.Point
+import android.os.PowerManager.WAKE_REASON_UNKNOWN
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -26,8 +27,9 @@
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.kosmos.testScope
-import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
-import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.power.data.repository.powerRepository
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.statusbar.CircleReveal
import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.testKosmos
@@ -51,7 +53,7 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository
- private val powerInteractor = kosmos.powerInteractor
+ private val powerRepository = kosmos.powerRepository
private lateinit var underTest: LightRevealScrimRepositoryImpl
@get:Rule val animatorTestRule = AnimatorTestRule(this)
@@ -63,7 +65,7 @@
LightRevealScrimRepositoryImpl(
kosmos.fakeKeyguardRepository,
context,
- kosmos.powerInteractor,
+ powerRepository,
mock()
)
}
@@ -73,7 +75,14 @@
val values = mutableListOf<LightRevealEffect>()
val job = launch { underTest.revealEffect.collect { values.add(it) } }
- powerInteractor.setAwakeForTest()
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.STARTING_TO_WAKE,
+ lastWakeReason = WakeSleepReason.fromPowerManagerWakeReason(WAKE_REASON_UNKNOWN),
+ powerButtonLaunchGestureTriggered =
+ powerRepository.wakefulness.value.powerButtonLaunchGestureTriggered,
+ )
+ powerRepository.updateWakefulness(rawState = WakefulnessState.AWAKE)
+
// We should initially emit the default reveal effect.
runCurrent()
values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT })
@@ -171,7 +180,7 @@
testScope.runTest {
val value by collectLastValue(underTest.revealAmount)
runCurrent()
- underTest.startRevealAmountAnimator(true)
+ underTest.startRevealAmountAnimator(true, 500L)
assertEquals(0.0f, value)
animatorTestRule.advanceTimeBy(500L)
assertEquals(1.0f, value)
@@ -183,11 +192,11 @@
testScope.runTest {
val value by collectLastValue(underTest.revealAmount)
runCurrent()
- underTest.startRevealAmountAnimator(true)
+ underTest.startRevealAmountAnimator(true, 500L)
assertEquals(0.0f, value)
animatorTestRule.advanceTimeBy(250L)
assertEquals(0.5f, value)
- underTest.startRevealAmountAnimator(true)
+ underTest.startRevealAmountAnimator(true, 500L)
animatorTestRule.advanceTimeBy(250L)
assertEquals(1.0f, value)
}
@@ -198,9 +207,9 @@
testScope.runTest {
val lastValue by collectLastValue(underTest.revealAmount)
runCurrent()
- underTest.startRevealAmountAnimator(true)
+ underTest.startRevealAmountAnimator(true, 500L)
animatorTestRule.advanceTimeBy(500L)
- underTest.startRevealAmountAnimator(false)
+ underTest.startRevealAmountAnimator(false, 500L)
animatorTestRule.advanceTimeBy(500L)
assertEquals(0.0f, lastValue)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
index 2b6e6c7..3e0a1f3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
@@ -24,7 +24,6 @@
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.testKosmos
@@ -50,7 +49,6 @@
private val fakeLightRevealScrimRepository = kosmos.fakeLightRevealScrimRepository
private val fakeKeyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
- private val testScope = kosmos.testScope
private val underTest = kosmos.lightRevealScrimInteractor
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelTest.kt
new file mode 100644
index 0000000..87d1cd5
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelTest.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.hardware.fingerprint.FingerprintManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.shared.model.FaceFailureMessage
+import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FingerprintFailureMessage
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AlternateBouncerMessageAreaViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val fingerprintAuthRepository by lazy {
+ kosmos.fakeDeviceEntryFingerprintAuthRepository
+ }
+ private val faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository }
+ private val bouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
+ private val biometricSettingsRepository by lazy { kosmos.fakeBiometricSettingsRepository }
+ private val underTest: AlternateBouncerMessageAreaViewModel =
+ kosmos.alternateBouncerMessageAreaViewModel
+
+ @Before
+ fun setUp() {
+ biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
+ biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true)
+ }
+
+ @Test
+ fun noInitialValue() =
+ testScope.runTest {
+ val message by collectLastValue(underTest.message)
+ bouncerRepository.setAlternateVisible(true)
+ assertThat(message).isNull()
+ }
+
+ @Test
+ fun fingerprintMessage() =
+ testScope.runTest {
+ val message by collectLastValue(underTest.message)
+ bouncerRepository.setAlternateVisible(true)
+ runCurrent()
+ fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+ assertThat(message).isInstanceOf(FingerprintFailureMessage::class.java)
+ }
+
+ @Test
+ fun fingerprintLockoutMessage_notShown() =
+ testScope.runTest {
+ val message by collectLastValue(underTest.message)
+ bouncerRepository.setAlternateVisible(true)
+ runCurrent()
+ fingerprintAuthRepository.setAuthenticationStatus(
+ ErrorFingerprintAuthenticationStatus(
+ msgId = FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
+ msg = "test lockout",
+ )
+ )
+ assertThat(message).isNull()
+ }
+
+ @Test
+ fun alternateBouncerNotVisible_messagesNeverShow() =
+ testScope.runTest {
+ val message by collectLastValue(underTest.message)
+ bouncerRepository.setAlternateVisible(false)
+ runCurrent()
+ fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+ assertThat(message).isNull()
+
+ faceAuthRepository.setAuthenticationStatus(FailedFaceAuthenticationStatus())
+ assertThat(message).isNull()
+ }
+
+ @Test
+ fun faceFailMessage() =
+ testScope.runTest {
+ val message by collectLastValue(underTest.message)
+ bouncerRepository.setAlternateVisible(true)
+ runCurrent()
+ faceAuthRepository.setAuthenticationStatus(FailedFaceAuthenticationStatus())
+ assertThat(message).isInstanceOf(FaceFailureMessage::class.java)
+ }
+
+ @Test
+ fun faceThenFingerprintMessage() =
+ testScope.runTest {
+ val message by collectLastValue(underTest.message)
+ bouncerRepository.setAlternateVisible(true)
+ runCurrent()
+ faceAuthRepository.setAuthenticationStatus(FailedFaceAuthenticationStatus())
+ assertThat(message).isInstanceOf(FaceFailureMessage::class.java)
+
+ fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+ assertThat(message).isInstanceOf(FingerprintFailureMessage::class.java)
+ }
+
+ @Test
+ fun fingerprintMessagePreventsFaceMessageFromShowing() =
+ testScope.runTest {
+ val message by collectLastValue(underTest.message)
+ bouncerRepository.setAlternateVisible(true)
+ runCurrent()
+ fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+ assertThat(message).isInstanceOf(FingerprintFailureMessage::class.java)
+
+ faceAuthRepository.setAuthenticationStatus(FailedFaceAuthenticationStatus())
+ assertThat(message).isInstanceOf(FingerprintFailureMessage::class.java)
+ }
+
+ @Test
+ fun fingerprintMessageAllowsFaceMessageAfter4000ms() =
+ testScope.runTest {
+ val message by collectLastValue(underTest.message)
+ bouncerRepository.setAlternateVisible(true)
+ runCurrent()
+ fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+ assertThat(message).isInstanceOf(FingerprintFailureMessage::class.java)
+
+ advanceTimeBy(4000)
+
+ faceAuthRepository.setAuthenticationStatus(FailedFaceAuthenticationStatus())
+ assertThat(message).isInstanceOf(FaceFailureMessage::class.java)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
index b0f59fe..de659cf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
@@ -71,7 +71,7 @@
mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
MockitoAnnotations.initMocks(this)
- whenever(burnInInteractor.keyguardBurnIn).thenReturn(burnInFlow)
+ whenever(burnInInteractor.burnIn(anyInt(), anyInt())).thenReturn(burnInFlow)
kosmos.burnInInteractor = burnInInteractor
whenever(goneToAodTransitionViewModel.enterFromTopTranslationY(anyInt()))
.thenReturn(emptyFlow())
@@ -81,18 +81,18 @@
}
@Test
- fun translationY_initializedToZero() =
+ fun movement_initializedToZero() =
testScope.runTest {
- val translationY by collectLastValue(underTest.translationY(burnInParameters))
- assertThat(translationY).isEqualTo(0)
+ val movement by collectLastValue(underTest.movement(burnInParameters))
+ assertThat(movement?.translationY).isEqualTo(0)
+ assertThat(movement?.translationX).isEqualTo(0)
+ assertThat(movement?.scale).isEqualTo(0f)
}
@Test
fun translationAndScale_whenNotDozing() =
testScope.runTest {
- val translationX by collectLastValue(underTest.translationX(burnInParameters))
- val translationY by collectLastValue(underTest.translationY(burnInParameters))
- val scale by collectLastValue(underTest.scale(burnInParameters))
+ val movement by collectLastValue(underTest.movement(burnInParameters))
// Set to not dozing (on lockscreen)
keyguardTransitionRepository.sendTransitionStep(
@@ -113,24 +113,17 @@
scale = 0.5f,
)
- assertThat(translationX).isEqualTo(0)
- assertThat(translationY).isEqualTo(0)
- assertThat(scale)
- .isEqualTo(
- BurnInScaleViewModel(
- scale = 1f,
- scaleClockOnly = true,
- )
- )
+ assertThat(movement?.translationX).isEqualTo(0)
+ assertThat(movement?.translationY).isEqualTo(0)
+ assertThat(movement?.scale).isEqualTo(1f)
+ assertThat(movement?.scaleClockOnly).isEqualTo(true)
}
@Test
fun translationAndScale_whenFullyDozing() =
testScope.runTest {
burnInParameters = burnInParameters.copy(minViewY = 100)
- val translationX by collectLastValue(underTest.translationX(burnInParameters))
- val translationY by collectLastValue(underTest.translationY(burnInParameters))
- val scale by collectLastValue(underTest.scale(burnInParameters))
+ val movement by collectLastValue(underTest.movement(burnInParameters))
// Set to dozing (on AOD)
keyguardTransitionRepository.sendTransitionStep(
@@ -150,15 +143,10 @@
scale = 0.5f,
)
- assertThat(translationX).isEqualTo(20)
- assertThat(translationY).isEqualTo(30)
- assertThat(scale)
- .isEqualTo(
- BurnInScaleViewModel(
- scale = 0.5f,
- scaleClockOnly = true,
- )
- )
+ assertThat(movement?.translationX).isEqualTo(20)
+ assertThat(movement?.translationY).isEqualTo(30)
+ assertThat(movement?.scale).isEqualTo(0.5f)
+ assertThat(movement?.scaleClockOnly).isEqualTo(true)
// Set to the beginning of GONE->AOD transition
keyguardTransitionRepository.sendTransitionStep(
@@ -170,15 +158,10 @@
),
validateStep = false,
)
- assertThat(translationX).isEqualTo(0)
- assertThat(translationY).isEqualTo(0)
- assertThat(scale)
- .isEqualTo(
- BurnInScaleViewModel(
- scale = 1f,
- scaleClockOnly = true,
- )
- )
+ assertThat(movement?.translationX).isEqualTo(0)
+ assertThat(movement?.translationY).isEqualTo(0)
+ assertThat(movement?.scale).isEqualTo(1f)
+ assertThat(movement?.scaleClockOnly).isEqualTo(true)
}
@Test
@@ -191,9 +174,7 @@
minViewY = 100,
topInset = 80,
)
- val translationX by collectLastValue(underTest.translationX(burnInParameters))
- val translationY by collectLastValue(underTest.translationY(burnInParameters))
- val scale by collectLastValue(underTest.scale(burnInParameters))
+ val movement by collectLastValue(underTest.movement(burnInParameters))
// Set to dozing (on AOD)
keyguardTransitionRepository.sendTransitionStep(
@@ -213,16 +194,11 @@
translationY = -30,
scale = 0.5f,
)
- assertThat(translationX).isEqualTo(20)
+ assertThat(movement?.translationX).isEqualTo(20)
// -20 instead of -30, due to inset of 80
- assertThat(translationY).isEqualTo(-20)
- assertThat(scale)
- .isEqualTo(
- BurnInScaleViewModel(
- scale = 0.5f,
- scaleClockOnly = true,
- )
- )
+ assertThat(movement?.translationY).isEqualTo(-20)
+ assertThat(movement?.scale).isEqualTo(0.5f)
+ assertThat(movement?.scaleClockOnly).isEqualTo(true)
// Set to the beginning of GONE->AOD transition
keyguardTransitionRepository.sendTransitionStep(
@@ -234,15 +210,10 @@
),
validateStep = false,
)
- assertThat(translationX).isEqualTo(0)
- assertThat(translationY).isEqualTo(0)
- assertThat(scale)
- .isEqualTo(
- BurnInScaleViewModel(
- scale = 1f,
- scaleClockOnly = true,
- )
- )
+ assertThat(movement?.translationX).isEqualTo(0)
+ assertThat(movement?.translationY).isEqualTo(0)
+ assertThat(movement?.scale).isEqualTo(1f)
+ assertThat(movement?.scaleClockOnly).isEqualTo(true)
}
@Test
@@ -255,9 +226,7 @@
minViewY = 100,
topInset = 80,
)
- val translationX by collectLastValue(underTest.translationX(burnInParameters))
- val translationY by collectLastValue(underTest.translationY(burnInParameters))
- val scale by collectLastValue(underTest.scale(burnInParameters))
+ val movement by collectLastValue(underTest.movement(burnInParameters))
// Set to dozing (on AOD)
keyguardTransitionRepository.sendTransitionStep(
@@ -277,16 +246,11 @@
translationY = -30,
scale = 0.5f,
)
- assertThat(translationX).isEqualTo(20)
+ assertThat(movement?.translationX).isEqualTo(20)
// -20 instead of -30, due to inset of 80
- assertThat(translationY).isEqualTo(-20)
- assertThat(scale)
- .isEqualTo(
- BurnInScaleViewModel(
- scale = 0.5f,
- scaleClockOnly = true,
- )
- )
+ assertThat(movement?.translationY).isEqualTo(-20)
+ assertThat(movement?.scale).isEqualTo(0.5f)
+ assertThat(movement?.scaleClockOnly).isEqualTo(true)
// Set to the beginning of GONE->AOD transition
keyguardTransitionRepository.sendTransitionStep(
@@ -298,15 +262,10 @@
),
validateStep = false,
)
- assertThat(translationX).isEqualTo(0)
- assertThat(translationY).isEqualTo(0)
- assertThat(scale)
- .isEqualTo(
- BurnInScaleViewModel(
- scale = 1f,
- scaleClockOnly = true,
- )
- )
+ assertThat(movement?.translationX).isEqualTo(0)
+ assertThat(movement?.translationY).isEqualTo(0)
+ assertThat(movement?.scale).isEqualTo(1f)
+ assertThat(movement?.scaleClockOnly).isEqualTo(true)
}
@Test
@@ -314,9 +273,7 @@
testScope.runTest {
whenever(clockController.config.useAlternateSmartspaceAODTransition).thenReturn(true)
- val translationX by collectLastValue(underTest.translationX(burnInParameters))
- val translationY by collectLastValue(underTest.translationY(burnInParameters))
- val scale by collectLastValue(underTest.scale(burnInParameters))
+ val movement by collectLastValue(underTest.movement(burnInParameters))
// Set to dozing (on AOD)
keyguardTransitionRepository.sendTransitionStep(
@@ -337,8 +294,9 @@
scale = 0.5f,
)
- assertThat(translationX).isEqualTo(0)
- assertThat(translationY).isEqualTo(0)
- assertThat(scale).isEqualTo(BurnInScaleViewModel(scale = 0.5f, scaleClockOnly = false))
+ assertThat(movement?.translationX).isEqualTo(0)
+ assertThat(movement?.translationY).isEqualTo(0)
+ assertThat(movement?.scale).isEqualTo(0.5f)
+ assertThat(movement?.scaleClockOnly).isEqualTo(false)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index 9da34da..e34edb4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -26,14 +26,9 @@
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
-import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
-import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
-import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -219,37 +214,6 @@
values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) }
}
- @Test
- fun transitionEnded() =
- testScope.runTest {
- val values by collectValues(underTest.transitionEnded)
-
- keyguardTransitionRepository.sendTransitionSteps(
- listOf(
- TransitionStep(DOZING, DREAMING, 0.0f, STARTED),
- TransitionStep(DOZING, DREAMING, 1.0f, FINISHED),
- TransitionStep(DREAMING, LOCKSCREEN, 0.0f, STARTED),
- TransitionStep(DREAMING, LOCKSCREEN, 0.1f, RUNNING),
- TransitionStep(DREAMING, LOCKSCREEN, 1.0f, FINISHED),
- TransitionStep(LOCKSCREEN, DREAMING, 0.0f, STARTED),
- TransitionStep(LOCKSCREEN, DREAMING, 0.5f, RUNNING),
- TransitionStep(LOCKSCREEN, DREAMING, 1.0f, FINISHED),
- TransitionStep(DREAMING, GONE, 0.0f, STARTED),
- TransitionStep(DREAMING, GONE, 0.5f, RUNNING),
- TransitionStep(DREAMING, GONE, 1.0f, CANCELED),
- TransitionStep(DREAMING, AOD, 0.0f, STARTED),
- TransitionStep(DREAMING, AOD, 1.0f, FINISHED),
- ),
- testScope,
- )
-
- assertThat(values.size).isEqualTo(3)
- values.forEach {
- assertThat(it.transitionState == FINISHED || it.transitionState == CANCELED)
- .isTrue()
- }
- }
-
private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep {
return TransitionStep(
from = DREAMING,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
new file mode 100644
index 0000000..04c270d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags as AConfigFlags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardIndicationAreaViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
+ @Mock private lateinit var shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel
+
+ @Mock private lateinit var burnInInteractor: BurnInInteractor
+ private val burnInFlow = MutableStateFlow(BurnInModel())
+
+ private lateinit var bottomAreaInteractor: KeyguardBottomAreaInteractor
+ private lateinit var underTest: KeyguardIndicationAreaViewModel
+ private lateinit var repository: FakeKeyguardRepository
+
+ private val startButtonFlow =
+ MutableStateFlow<KeyguardQuickAffordanceViewModel>(
+ KeyguardQuickAffordanceViewModel(
+ slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()
+ )
+ )
+ private val endButtonFlow =
+ MutableStateFlow<KeyguardQuickAffordanceViewModel>(
+ KeyguardQuickAffordanceViewModel(
+ slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId()
+ )
+ )
+ private val alphaFlow = MutableStateFlow<Float>(1f)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
+ mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+
+ whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
+ .thenReturn(RETURNED_BURN_IN_OFFSET)
+ whenever(burnInInteractor.burnIn(anyInt(), anyInt())).thenReturn(burnInFlow)
+
+ val withDeps = KeyguardInteractorFactory.create()
+ val keyguardInteractor = withDeps.keyguardInteractor
+ repository = withDeps.repository
+
+ val bottomAreaViewModel: KeyguardBottomAreaViewModel = mock()
+ whenever(bottomAreaViewModel.startButton).thenReturn(startButtonFlow)
+ whenever(bottomAreaViewModel.endButton).thenReturn(endButtonFlow)
+ whenever(bottomAreaViewModel.alpha).thenReturn(alphaFlow)
+ bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository)
+ underTest =
+ KeyguardIndicationAreaViewModel(
+ keyguardInteractor = keyguardInteractor,
+ bottomAreaInteractor = bottomAreaInteractor,
+ keyguardBottomAreaViewModel = bottomAreaViewModel,
+ burnInHelperWrapper = burnInHelperWrapper,
+ burnInInteractor = burnInInteractor,
+ shortcutsCombinedViewModel = shortcutsCombinedViewModel,
+ configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()),
+ )
+ }
+
+ @Test
+ fun alpha() =
+ testScope.runTest {
+ val value = collectLastValue(underTest.alpha)
+
+ assertThat(value()).isEqualTo(1f)
+ alphaFlow.value = 0.1f
+ assertThat(value()).isEqualTo(0.1f)
+ alphaFlow.value = 0.5f
+ assertThat(value()).isEqualTo(0.5f)
+ alphaFlow.value = 0.2f
+ assertThat(value()).isEqualTo(0.2f)
+ alphaFlow.value = 0f
+ assertThat(value()).isEqualTo(0f)
+ }
+
+ @Test
+ fun isIndicationAreaPadded() =
+ testScope.runTest {
+ repository.setKeyguardShowing(true)
+ val value = collectLastValue(underTest.isIndicationAreaPadded)
+
+ assertThat(value()).isFalse()
+ startButtonFlow.value = startButtonFlow.value.copy(isVisible = true)
+ assertThat(value()).isTrue()
+ endButtonFlow.value = endButtonFlow.value.copy(isVisible = true)
+ assertThat(value()).isTrue()
+ startButtonFlow.value = startButtonFlow.value.copy(isVisible = false)
+ assertThat(value()).isTrue()
+ endButtonFlow.value = endButtonFlow.value.copy(isVisible = false)
+ assertThat(value()).isFalse()
+ }
+
+ @Test
+ fun indicationAreaTranslationX() =
+ testScope.runTest {
+ val value = collectLastValue(underTest.indicationAreaTranslationX)
+
+ assertThat(value()).isEqualTo(0f)
+ bottomAreaInteractor.setClockPosition(100, 100)
+ assertThat(value()).isEqualTo(100f)
+ bottomAreaInteractor.setClockPosition(200, 100)
+ assertThat(value()).isEqualTo(200f)
+ bottomAreaInteractor.setClockPosition(200, 200)
+ assertThat(value()).isEqualTo(200f)
+ bottomAreaInteractor.setClockPosition(300, 100)
+ assertThat(value()).isEqualTo(300f)
+ }
+
+ @Test
+ fun indicationAreaTranslationY() =
+ testScope.runTest {
+ val value =
+ collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET))
+
+ // Negative 0 - apparently there's a difference in floating point arithmetic - FML
+ assertThat(value()).isEqualTo(-0f)
+ val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f)
+ assertThat(value()).isEqualTo(expected1)
+ val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f)
+ assertThat(value()).isEqualTo(expected2)
+ val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f)
+ assertThat(value()).isEqualTo(expected3)
+ val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f)
+ assertThat(value()).isEqualTo(expected4)
+ }
+
+ private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
+ repository.setDozeAmount(dozeAmount)
+ return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET)
+ }
+
+ companion object {
+ private const val DEFAULT_BURN_IN_OFFSET = 5
+ private const val RETURNED_BURN_IN_OFFSET = 3
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 9ff76be..2fd2ef1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -19,9 +19,12 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.platform.test.annotations.EnableFlags
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -33,63 +36,127 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.Parameter
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(ParameterizedAndroidJunit4::class)
class LockscreenSceneViewModelTest : SysuiTestCase() {
+ companion object {
+ @Parameters(
+ name =
+ "canSwipeToEnter={0}, downWithTwoPointers={1}, downFromEdge={2}," +
+ " isSingleShade={3}, isCommunalAvailable={4}"
+ )
+ @JvmStatic
+ fun combinations() = buildList {
+ repeat(32) { combination ->
+ add(
+ arrayOf(
+ /* canSwipeToEnter= */ combination and 1 != 0,
+ /* downWithTwoPointers= */ combination and 2 != 0,
+ /* downFromEdge= */ combination and 4 != 0,
+ /* isSingleShade= */ combination and 8 != 0,
+ /* isCommunalAvailable= */ combination and 16 != 0,
+ )
+ )
+ }
+ }
+
+ @JvmStatic
+ @BeforeClass
+ fun setUp() {
+ val combinationStrings =
+ combinations().map { array ->
+ check(array.size == 5)
+ "${array[4]},${array[3]},${array[2]},${array[1]},${array[0]}"
+ }
+ val uniqueCombinations = combinationStrings.toSet()
+ assertThat(combinationStrings).hasSize(uniqueCombinations.size)
+ }
+
+ private fun expectedDownDestination(
+ downFromEdge: Boolean,
+ isSingleShade: Boolean,
+ ): SceneKey {
+ return if (downFromEdge && isSingleShade) Scenes.QuickSettings else Scenes.Shade
+ }
+ }
+
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ @JvmField @Parameter(0) var canSwipeToEnter: Boolean = false
+ @JvmField @Parameter(1) var downWithTwoPointers: Boolean = false
+ @JvmField @Parameter(2) var downFromEdge: Boolean = false
+ @JvmField @Parameter(3) var isSingleShade: Boolean = true
+ @JvmField @Parameter(4) var isCommunalAvailable: Boolean = false
+
private val underTest by lazy { createLockscreenSceneViewModel() }
@Test
- fun upTransitionSceneKey_canSwipeToUnlock_gone() =
+ @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
+ fun destinationScenes() =
testScope.runTest {
- val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.None
- )
kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
- sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
-
- assertThat(upTransitionSceneKey).isEqualTo(Scenes.Gone)
- }
-
- @Test
- fun upTransitionSceneKey_cannotSwipeToUnlock_bouncer() =
- testScope.runTest {
- val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin
+ if (canSwipeToEnter) {
+ AuthenticationMethodModel.None
+ } else {
+ AuthenticationMethodModel.Pin
+ }
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(canSwipeToEnter)
sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
+ kosmos.shadeRepository.setShadeMode(
+ if (isSingleShade) {
+ ShadeMode.Single
+ } else {
+ ShadeMode.Split
+ }
+ )
+ kosmos.setCommunalAvailable(isCommunalAvailable)
- assertThat(upTransitionSceneKey).isEqualTo(Scenes.Bouncer)
- }
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
- @EnableFlags(FLAG_COMMUNAL_HUB)
- @Test
- fun leftTransitionSceneKey_communalIsAvailable_communal() =
- testScope.runTest {
- val leftDestinationSceneKey by collectLastValue(underTest.leftDestinationSceneKey)
- assertThat(leftDestinationSceneKey).isNull()
+ assertThat(
+ destinationScenes
+ ?.get(
+ Swipe(
+ SwipeDirection.Down,
+ fromSource = Edge.Top.takeIf { downFromEdge },
+ pointerCount = if (downWithTwoPointers) 2 else 1,
+ )
+ )
+ ?.toScene
+ )
+ .isEqualTo(
+ expectedDownDestination(
+ downFromEdge = downFromEdge,
+ isSingleShade = isSingleShade,
+ )
+ )
- kosmos.setCommunalAvailable(true)
- runCurrent()
- assertThat(leftDestinationSceneKey).isEqualTo(Scenes.Communal)
+ assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
+ .isEqualTo(if (canSwipeToEnter) Scenes.Gone else Scenes.Bouncer)
+
+ assertThat(destinationScenes?.get(Swipe(SwipeDirection.Left))?.toScene)
+ .isEqualTo(Scenes.Communal.takeIf { isCommunalAvailable })
}
private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel {
@@ -102,6 +169,7 @@
interactor = mock(),
),
notifications = kosmos.notificationsPlaceholderViewModel,
+ shadeInteractor = kosmos.shadeInteractor,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
index a1f885c..c0e5a9b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
@@ -114,7 +114,7 @@
DisabledByPolicyInteractor.PolicyResult.TileDisabled(ADMIN)
)
- val expectedIntent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(context, ADMIN)
+ val expectedIntent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(ADMIN)
assertThat(result).isTrue()
verify(activityStarter).postStartActivityDismissingKeyguard(intentCaptor.capture(), any())
assertThat(intentCaptor.value.filterEquals(expectedIntent)).isTrue()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 63f00c1..6108904 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
+import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.privacyChipInteractor
import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
@@ -108,14 +109,15 @@
shadeHeaderViewModel = shadeHeaderViewModel,
qsSceneAdapter = qsFlexiglassAdapter,
notifications = kosmos.notificationsPlaceholderViewModel,
- footerActionsViewModelFactory,
- footerActionsController,
+ footerActionsViewModelFactory = footerActionsViewModelFactory,
+ footerActionsController = footerActionsController,
)
}
@Test
fun destinationsNotCustomizing() =
testScope.runTest {
+ overrideResource(R.bool.config_use_split_notification_shade, false)
val destinations by collectLastValue(underTest.destinationScenes)
qsFlexiglassAdapter.setCustomizing(false)
@@ -131,6 +133,38 @@
@Test
fun destinationsCustomizing() =
testScope.runTest {
+ overrideResource(R.bool.config_use_split_notification_shade, false)
+ val destinations by collectLastValue(underTest.destinationScenes)
+ qsFlexiglassAdapter.setCustomizing(true)
+
+ assertThat(destinations)
+ .isEqualTo(
+ mapOf(
+ Back to UserActionResult(Scenes.QuickSettings),
+ )
+ )
+ }
+
+ @Test
+ fun destinations_whenNotCustomizing_inSplitShade() =
+ testScope.runTest {
+ overrideResource(R.bool.config_use_split_notification_shade, true)
+ val destinations by collectLastValue(underTest.destinationScenes)
+ qsFlexiglassAdapter.setCustomizing(false)
+
+ assertThat(destinations)
+ .isEqualTo(
+ mapOf(
+ Back to UserActionResult(Scenes.Shade),
+ Swipe(SwipeDirection.Up) to UserActionResult(Scenes.Shade),
+ )
+ )
+ }
+
+ @Test
+ fun destinations_whenCustomizing_inSplitShade() =
+ testScope.runTest {
+ overrideResource(R.bool.config_use_split_notification_shade, true)
val destinations by collectLastValue(underTest.destinationScenes)
qsFlexiglassAdapter.setCustomizing(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index a2c4f4e..af9abcd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -20,10 +20,12 @@
import android.telecom.TelecomManager
import android.telephony.TelephonyManager
+import android.testing.TestableLooper.RunWithLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.Swipe
import com.android.internal.R
import com.android.internal.util.EmergencyAffordanceManager
import com.android.internal.util.emergencyAffordanceManager
@@ -59,6 +61,8 @@
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.qs.footerActionsController
+import com.android.systemui.qs.footerActionsViewModelFactory
import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.startable.SceneContainerStartable
@@ -69,6 +73,7 @@
import com.android.systemui.settings.FakeDisplayTracker
import com.android.systemui.shade.domain.interactor.privacyChipInteractor
import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
@@ -127,6 +132,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@RunWithLooper
class SceneFrameworkIntegrationTest : SysuiTestCase() {
private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
@@ -167,6 +173,7 @@
interactor = mock(),
),
notifications = kosmos.notificationsPlaceholderViewModel,
+ shadeInteractor = kosmos.shadeInteractor,
)
}
@@ -250,6 +257,9 @@
qsSceneAdapter = qsFlexiglassAdapter,
notifications = kosmos.notificationsPlaceholderViewModel,
mediaDataManager = mediaDataManager,
+ shadeInteractor = kosmos.shadeInteractor,
+ footerActionsController = kosmos.footerActionsController,
+ footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory,
)
kosmos.fakeDeviceEntryRepository.setUnlocked(false)
@@ -306,8 +316,8 @@
@Test
fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() =
testScope.runTest {
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -326,8 +336,8 @@
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -337,7 +347,7 @@
@Test
fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
testScope.runTest {
- val upDestinationSceneKey by collectLastValue(shadeSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(shadeSceneViewModel.destinationScenes)
setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
assertCurrentScene(Scenes.Lockscreen)
@@ -345,6 +355,7 @@
emulateUserDrivenTransition(to = Scenes.Shade)
assertCurrentScene(Scenes.Shade)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Lockscreen)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -354,7 +365,7 @@
@Test
fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenDismissed_goesToGone() =
testScope.runTest {
- val upDestinationSceneKey by collectLastValue(shadeSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(shadeSceneViewModel.destinationScenes)
setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
assertThat(deviceEntryInteractor.canSwipeToEnter.value).isTrue()
assertCurrentScene(Scenes.Lockscreen)
@@ -367,6 +378,7 @@
emulateUserDrivenTransition(to = Scenes.Shade)
assertCurrentScene(Scenes.Shade)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -434,8 +446,8 @@
fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() =
testScope.runTest {
unlockDevice()
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
}
@@ -456,8 +468,8 @@
fun dismissingIme_whileOnPasswordBouncer_navigatesToLockscreen() =
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.Password)
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -474,8 +486,8 @@
fun bouncerActionButtonClick_opensEmergencyServicesDialer() =
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.Password)
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(to = upDestinationSceneKey)
@@ -494,8 +506,8 @@
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.Password)
startPhoneCall()
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(to = upDestinationSceneKey)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
index d3fa360..cd79ed1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
@@ -54,7 +54,7 @@
private val kosmos = Kosmos()
private val testScope = kosmos.testScope
private val sceneInteractor = kosmos.sceneInteractor
- private val deviceEntryInteractor = kosmos.deviceEntryInteractor
+ private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
private lateinit var shadeInteractor: ShadeInteractor
private lateinit var underTest: ShadeControllerSceneImpl
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/PanelExpansionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
similarity index 78%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/PanelExpansionInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
index 6b5997f..16b68cc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/PanelExpansionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt
@@ -16,24 +16,21 @@
@file:OptIn(ExperimentalCoroutinesApi::class)
-package com.android.systemui.scene.domain.interactor
+package com.android.systemui.shade.domain.interactor
-import android.platform.test.annotations.DisableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
-import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
-import com.android.systemui.shade.data.repository.fakeShadeRepository
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.panelExpansionInteractor
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -48,7 +45,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
-class PanelExpansionInteractorTest : SysuiTestCase() {
+class PanelExpansionInteractorImplTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -60,9 +57,8 @@
ObservableTransitionState.Idle(Scenes.Lockscreen)
)
private val fakeSceneDataSource = kosmos.fakeSceneDataSource
- private val fakeShadeRepository = kosmos.fakeShadeRepository
- private lateinit var underTest: PanelExpansionInteractor
+ private lateinit var underTest: PanelExpansionInteractorImpl
@Before
fun setUp() {
@@ -73,7 +69,7 @@
@EnableSceneContainer
fun legacyPanelExpansion_whenIdle_whenLocked() =
testScope.runTest {
- underTest = kosmos.panelExpansionInteractor
+ underTest = kosmos.panelExpansionInteractorImpl
setUnlocked(false)
val panelExpansion by collectLastValue(underTest.legacyPanelExpansion)
@@ -97,7 +93,7 @@
@EnableSceneContainer
fun legacyPanelExpansion_whenIdle_whenUnlocked() =
testScope.runTest {
- underTest = kosmos.panelExpansionInteractor
+ underTest = kosmos.panelExpansionInteractorImpl
setUnlocked(true)
val panelExpansion by collectLastValue(underTest.legacyPanelExpansion)
@@ -116,33 +112,6 @@
changeScene(Scenes.Communal) { assertThat(panelExpansion).isEqualTo(1f) }
assertThat(panelExpansion).isEqualTo(1f)
}
-
- @Test
- @DisableFlags(FLAG_SCENE_CONTAINER)
- fun legacyPanelExpansion_whenInLegacyMode() =
- testScope.runTest {
- underTest = kosmos.panelExpansionInteractor
- val leet = 0.1337f
- fakeShadeRepository.setLegacyShadeExpansion(leet)
- setUnlocked(false)
- val panelExpansion by collectLastValue(underTest.legacyPanelExpansion)
-
- changeScene(Scenes.Lockscreen)
- assertThat(panelExpansion).isEqualTo(leet)
-
- changeScene(Scenes.Bouncer)
- assertThat(panelExpansion).isEqualTo(leet)
-
- changeScene(Scenes.Shade)
- assertThat(panelExpansion).isEqualTo(leet)
-
- changeScene(Scenes.QuickSettings)
- assertThat(panelExpansion).isEqualTo(leet)
-
- changeScene(Scenes.Communal)
- assertThat(panelExpansion).isEqualTo(leet)
- }
-
private fun TestScope.setUnlocked(isUnlocked: Boolean) {
val isDeviceUnlocked by collectLastValue(deviceUnlockedInteractor.isDeviceUnlocked)
deviceEntryRepository.setUnlocked(isUnlocked)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
index b662133..d309c6b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
@@ -36,12 +36,14 @@
import kotlinx.coroutines.test.runTest
import org.junit.Assume
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@Ignore("b/328827631")
class ShadeBackActionInteractorImplTest : SysuiTestCase() {
val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
val testScope = kosmos.testScope
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
index 4cd2c30..8c9036a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
@@ -29,9 +29,12 @@
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.userRepository
import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
@@ -45,19 +48,20 @@
@RunWith(AndroidJUnit4::class)
class ShadeInteractorSceneContainerImplTest : SysuiTestCase() {
- val kosmos = testKosmos()
- val testComponent = kosmos.testScope
- val configurationRepository = kosmos.fakeConfigurationRepository
- val keyguardRepository = kosmos.fakeKeyguardRepository
- val keyguardTransitionRepository = kosmos.keyguardTransitionRepository
- val sceneInteractor = kosmos.sceneInteractor
- val userRepository = kosmos.userRepository
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val configurationRepository = kosmos.fakeConfigurationRepository
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
+ private val keyguardTransitionRepository = kosmos.keyguardTransitionRepository
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val userRepository = kosmos.userRepository
+ private val shadeRepository = kosmos.shadeRepository
- val underTest = kosmos.shadeInteractorSceneContainerImpl
+ private val underTest = kosmos.shadeInteractorSceneContainerImpl
@Test
fun qsExpansionWhenInSplitShadeAndQsExpanded() =
- testComponent.runTest {
+ testScope.runTest {
val actual by collectLastValue(underTest.qsExpansion)
// WHEN split shade is enabled and QS is expanded
@@ -84,7 +88,7 @@
@Test
fun qsExpansionWhenNotInSplitShadeAndQsExpanded() =
- testComponent.runTest {
+ testScope.runTest {
val actual by collectLastValue(underTest.qsExpansion)
// WHEN split shade is not enabled and QS is expanded
@@ -112,7 +116,7 @@
@Test
fun qsFullscreen_falseWhenTransitioning() =
- testComponent.runTest {
+ testScope.runTest {
val actual by collectLastValue(underTest.isQsFullscreen)
// WHEN scene transition active
@@ -136,7 +140,7 @@
@Test
fun qsFullscreen_falseWhenIdleNotQS() =
- testComponent.runTest {
+ testScope.runTest {
val actual by collectLastValue(underTest.isQsFullscreen)
// WHEN Idle but not on QuickSettings scene
@@ -154,7 +158,7 @@
@Test
fun qsFullscreen_trueWhenIdleQS() =
- testComponent.runTest {
+ testScope.runTest {
val actual by collectLastValue(underTest.isQsFullscreen)
// WHEN Idle on QuickSettings scene
@@ -172,7 +176,7 @@
@Test
fun lockscreenShadeExpansion_idle_onScene() =
- testComponent.runTest {
+ testScope.runTest {
// GIVEN an expansion flow based on transitions to and from a scene
val key = Scenes.Shade
val expansion = underTest.sceneBasedExpansion(sceneInteractor, key)
@@ -189,7 +193,7 @@
@Test
fun lockscreenShadeExpansion_idle_onDifferentScene() =
- testComponent.runTest {
+ testScope.runTest {
// GIVEN an expansion flow based on transitions to and from a scene
val expansion = underTest.sceneBasedExpansion(sceneInteractor, Scenes.Shade)
val expansionAmount by collectLastValue(expansion)
@@ -207,7 +211,7 @@
@Test
fun lockscreenShadeExpansion_transitioning_toScene() =
- testComponent.runTest {
+ testScope.runTest {
// GIVEN an expansion flow based on transitions to and from a scene
val key = Scenes.QuickSettings
val expansion = underTest.sceneBasedExpansion(sceneInteractor, key)
@@ -245,7 +249,7 @@
@Test
fun lockscreenShadeExpansion_transitioning_fromScene() =
- testComponent.runTest {
+ testScope.runTest {
// GIVEN an expansion flow based on transitions to and from a scene
val key = Scenes.QuickSettings
val expansion = underTest.sceneBasedExpansion(sceneInteractor, key)
@@ -282,7 +286,7 @@
}
fun isQsBypassingShade_goneToQs() =
- testComponent.runTest {
+ testScope.runTest {
val actual by collectLastValue(underTest.isQsBypassingShade)
// WHEN transitioning from QS directly to Gone
@@ -305,7 +309,7 @@
}
fun isQsBypassingShade_shadeToQs() =
- testComponent.runTest {
+ testScope.runTest {
val actual by collectLastValue(underTest.isQsBypassingShade)
// WHEN transitioning from QS to Shade
@@ -329,7 +333,7 @@
@Test
fun lockscreenShadeExpansion_transitioning_toAndFromDifferentScenes() =
- testComponent.runTest {
+ testScope.runTest {
// GIVEN an expansion flow based on transitions to and from a scene
val expansion = underTest.sceneBasedExpansion(sceneInteractor, Scenes.QuickSettings)
val expansionAmount by collectLastValue(expansion)
@@ -366,7 +370,7 @@
@Test
fun userInteracting_idle() =
- testComponent.runTest {
+ testScope.runTest {
// GIVEN an interacting flow based on transitions to and from a scene
val key = Scenes.Shade
val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
@@ -383,7 +387,7 @@
@Test
fun userInteracting_transitioning_toScene_programmatic() =
- testComponent.runTest {
+ testScope.runTest {
// GIVEN an interacting flow based on transitions to and from a scene
val key = Scenes.QuickSettings
val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
@@ -421,7 +425,7 @@
@Test
fun userInteracting_transitioning_toScene_userInputDriven() =
- testComponent.runTest {
+ testScope.runTest {
// GIVEN an interacting flow based on transitions to and from a scene
val key = Scenes.QuickSettings
val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
@@ -459,7 +463,7 @@
@Test
fun userInteracting_transitioning_fromScene_programmatic() =
- testComponent.runTest {
+ testScope.runTest {
// GIVEN an interacting flow based on transitions to and from a scene
val key = Scenes.QuickSettings
val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
@@ -497,7 +501,7 @@
@Test
fun userInteracting_transitioning_fromScene_userInputDriven() =
- testComponent.runTest {
+ testScope.runTest {
// GIVEN an interacting flow based on transitions to and from a scene
val key = Scenes.QuickSettings
val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
@@ -535,7 +539,7 @@
@Test
fun userInteracting_transitioning_toAndFromDifferentScenes() =
- testComponent.runTest {
+ testScope.runTest {
// GIVEN an interacting flow based on transitions to and from a scene
val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, Scenes.Shade)
val interacting by collectLastValue(interactingFlow)
@@ -557,4 +561,19 @@
// THEN interacting is false
Truth.assertThat(interacting).isFalse()
}
+
+ @Test
+ fun shadeMode() =
+ testScope.runTest {
+ val shadeMode by collectLastValue(underTest.shadeMode)
+
+ shadeRepository.setShadeMode(ShadeMode.Split)
+ assertThat(shadeMode).isEqualTo(ShadeMode.Split)
+
+ shadeRepository.setShadeMode(ShadeMode.Single)
+ assertThat(shadeMode).isEqualTo(ShadeMode.Single)
+
+ shadeRepository.setShadeMode(ShadeMode.Split)
+ assertThat(shadeMode).isEqualTo(ShadeMode.Split)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
new file mode 100644
index 0000000..31dacdd
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.startable
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.testKosmos
+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 ShadeStartableTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val shadeInteractor = kosmos.shadeInteractor
+ private val fakeConfigurationRepository = kosmos.fakeConfigurationRepository
+
+ private val underTest = kosmos.shadeStartable
+
+ @Test
+ fun hydrateShadeMode() =
+ testScope.runTest {
+ overrideResource(R.bool.config_use_split_notification_shade, false)
+ val shadeMode by collectLastValue(shadeInteractor.shadeMode)
+
+ underTest.start()
+ assertThat(shadeMode).isEqualTo(ShadeMode.Single)
+
+ overrideResource(R.bool.config_use_split_notification_shade, true)
+ fakeConfigurationRepository.onAnyConfigurationChange()
+ assertThat(shadeMode).isEqualTo(ShadeMode.Split)
+
+ overrideResource(R.bool.config_use_split_notification_shade, false)
+ fakeConfigurationRepository.onAnyConfigurationChange()
+ assertThat(shadeMode).isEqualTo(ShadeMode.Single)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 853b00d..1c54961 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -16,8 +16,11 @@
package com.android.systemui.shade.ui.viewmodel
+import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -28,11 +31,18 @@
import com.android.systemui.flags.Flags
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.qs.footerActionsController
+import com.android.systemui.qs.footerActionsViewModelFactory
import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
+import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.privacyChipInteractor
import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.domain.startable.shadeStartable
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
@@ -57,12 +67,14 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
class ShadeSceneViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
+ private val shadeRepository by lazy { kosmos.shadeRepository }
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
@@ -113,50 +125,56 @@
qsSceneAdapter = qsFlexiglassAdapter,
notifications = kosmos.notificationsPlaceholderViewModel,
mediaDataManager = mediaDataManager,
+ shadeInteractor = kosmos.shadeInteractor,
+ footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory,
+ footerActionsController = kosmos.footerActionsController,
)
}
@Test
fun upTransitionSceneKey_deviceLocked_lockScreen() =
testScope.runTest {
- val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin
)
kosmos.fakeDeviceEntryRepository.setUnlocked(false)
- assertThat(upTransitionSceneKey).isEqualTo(Scenes.Lockscreen)
+ assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
+ .isEqualTo(Scenes.Lockscreen)
}
@Test
fun upTransitionSceneKey_deviceUnlocked_gone() =
testScope.runTest {
- val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pin
)
kosmos.fakeDeviceEntryRepository.setUnlocked(true)
- assertThat(upTransitionSceneKey).isEqualTo(Scenes.Gone)
+ assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
+ .isEqualTo(Scenes.Gone)
}
@Test
fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
testScope.runTest {
- val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.None
)
sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
- assertThat(upTransitionSceneKey).isEqualTo(Scenes.Lockscreen)
+ assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
+ .isEqualTo(Scenes.Lockscreen)
}
@Test
fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
testScope.runTest {
- val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
kosmos.fakeDeviceEntryRepository.setUnlocked(true)
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
@@ -165,7 +183,8 @@
runCurrent()
sceneInteractor.changeScene(Scenes.Gone, "reason")
- assertThat(upTransitionSceneKey).isEqualTo(Scenes.Gone)
+ assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
+ .isEqualTo(Scenes.Gone)
}
@Test
@@ -239,4 +258,38 @@
assertThat(underTest.isMediaVisible()).isFalse()
}
+
+ @Test
+ fun downTransitionSceneKey_inSplitShade_null() =
+ testScope.runTest {
+ overrideResource(R.bool.config_use_split_notification_shade, true)
+ kosmos.shadeStartable.start()
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+ assertThat(destinationScenes?.get(Swipe(SwipeDirection.Down))?.toScene).isNull()
+ }
+
+ @Test
+ fun downTransitionSceneKey_notSplitShade_quickSettings() =
+ testScope.runTest {
+ overrideResource(R.bool.config_use_split_notification_shade, false)
+ kosmos.shadeStartable.start()
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+ assertThat(destinationScenes?.get(Swipe(SwipeDirection.Down))?.toScene)
+ .isEqualTo(Scenes.QuickSettings)
+ }
+
+ @Test
+ fun shadeMode() =
+ testScope.runTest {
+ val shadeMode by collectLastValue(underTest.shadeMode)
+
+ shadeRepository.setShadeMode(ShadeMode.Split)
+ assertThat(shadeMode).isEqualTo(ShadeMode.Split)
+
+ shadeRepository.setShadeMode(ShadeMode.Single)
+ assertThat(shadeMode).isEqualTo(ShadeMode.Single)
+
+ shadeRepository.setShadeMode(ShadeMode.Split)
+ assertThat(shadeMode).isEqualTo(ShadeMode.Split)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt
index e188f5b..8e765f7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt
@@ -196,10 +196,14 @@
}
@Test
- fun zenModeAlarms_ringAndNotifications_muted() {
+ fun zenModeAlarms_ringedStreams_muted() {
with(kosmos) {
val expectedToBeMuted =
- setOf(AudioManager.STREAM_RING, AudioManager.STREAM_NOTIFICATION)
+ setOf(
+ AudioManager.STREAM_RING,
+ AudioManager.STREAM_NOTIFICATION,
+ AudioManager.STREAM_SYSTEM,
+ )
testScope.runTest {
notificationsSoundPolicyRepository.updateNotificationPolicy()
notificationsSoundPolicyRepository.updateZenMode(ZenMode(Global.ZEN_MODE_ALARMS))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 0de15b8..278bf9b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -21,25 +21,24 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
-import com.android.systemui.communal.domain.interactor.communalInteractor
-import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
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.BurnInModel
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.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
+import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel
import com.android.systemui.kosmos.testScope
@@ -66,7 +65,7 @@
@RunWith(AndroidJUnit4::class)
class SharedNotificationContainerViewModelTest : SysuiTestCase() {
val aodBurnInViewModel = mock(AodBurnInViewModel::class.java)
- lateinit var translationYFlow: MutableStateFlow<Float>
+ lateinit var movementFlow: MutableStateFlow<BurnInModel>
val kosmos =
testKosmos().apply {
@@ -79,13 +78,13 @@
init {
kosmos.aodBurnInViewModel = aodBurnInViewModel
}
+
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 communalInteractor = kosmos.communalInteractor
val shadeRepository = kosmos.shadeRepository
val sharedNotificationContainerInteractor = kosmos.sharedNotificationContainerInteractor
val largeScreenHeaderHelper = kosmos.mockLargeScreenHeaderHelper
@@ -95,8 +94,8 @@
@Before
fun setUp() {
overrideResource(R.bool.config_use_split_notification_shade, false)
- translationYFlow = MutableStateFlow(0f)
- whenever(aodBurnInViewModel.translationY(any())).thenReturn(translationYFlow)
+ movementFlow = MutableStateFlow(BurnInModel())
+ whenever(aodBurnInViewModel.movement(any())).thenReturn(movementFlow)
underTest = kosmos.sharedNotificationContainerViewModel
}
@@ -238,7 +237,7 @@
}
@Test
- fun glanceableHubAlpha() =
+ fun glanceableHubAlpha_lockscreenToHub() =
testScope.runTest {
val alpha by collectLastValue(underTest.glanceableHubAlpha)
@@ -277,12 +276,6 @@
value = 1f,
)
)
- val idleTransitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(CommunalScenes.Communal)
- )
- communalInteractor.setTransitionState(idleTransitionState)
- runCurrent()
assertThat(alpha).isEqualTo(0f)
// While state is GLANCEABLE_HUB, verify alpha is restored to full if glanceable hub is
@@ -292,6 +285,50 @@
}
@Test
+ fun glanceableHubAlpha_dreamToHub() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.glanceableHubAlpha)
+
+ // Start on dream
+ showDream()
+ assertThat(alpha).isEqualTo(1f)
+
+ // Start transitioning to glanceable hub
+ val progress = 0.6f
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ value = 0f,
+ )
+ )
+ runCurrent()
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.RUNNING,
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ value = progress,
+ )
+ )
+ runCurrent()
+ // Keep notifications hidden during the transition from dream to hub
+ assertThat(alpha).isEqualTo(0)
+
+ // Finish transition to glanceable hub
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ value = 1f,
+ )
+ )
+ assertThat(alpha).isEqualTo(0f)
+ }
+
+ @Test
fun validateMarginTop() =
testScope.runTest {
overrideResource(R.bool.config_use_large_screen_shade_header, false)
@@ -390,12 +427,11 @@
assertThat(isOnGlanceableHubWithoutShade).isFalse()
// Move to glanceable hub
- val idleTransitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(CommunalScenes.Communal)
- )
- communalInteractor.setTransitionState(idleTransitionState)
- runCurrent()
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = this
+ )
assertThat(isOnGlanceableHubWithoutShade).isTrue()
// While state is GLANCEABLE_HUB, validate variations of both shade and qs expansion
@@ -608,7 +644,7 @@
showLockscreen()
assertThat(translationY).isEqualTo(0)
- translationYFlow.value = 150f
+ movementFlow.value = BurnInModel(translationY = 150)
assertThat(translationY).isEqualTo(150f)
}
@@ -664,6 +700,25 @@
}
@Test
+ fun alphaOnFullQsExpansion() =
+ testScope.runTest {
+ val viewState = ViewStateAccessor()
+ val alpha by collectLastValue(underTest.keyguardAlpha(viewState))
+
+ showLockscreenWithQSExpanded()
+
+ // Alpha fades out as QS expands
+ shadeRepository.setQsExpansion(0.5f)
+ assertThat(alpha).isWithin(0.01f).of(0.5f)
+ shadeRepository.setQsExpansion(0.9f)
+ assertThat(alpha).isWithin(0.01f).of(0.1f)
+
+ // Ensure that alpha is set back to 1f when QS is fully expanded
+ shadeRepository.setQsExpansion(1f)
+ assertThat(alpha).isEqualTo(1f)
+ }
+
+ @Test
fun shadeCollapseFadeIn() =
testScope.runTest {
val fadeIn by collectLastValue(underTest.shadeCollapseFadeIn)
@@ -725,6 +780,19 @@
)
}
+ private suspend fun TestScope.showDream() {
+ shadeRepository.setLockscreenShadeExpansion(0f)
+ shadeRepository.setQsExpansion(0f)
+ runCurrent()
+ keyguardRepository.setDreaming(true)
+ runCurrent()
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ testScope,
+ )
+ }
+
private suspend fun TestScope.showLockscreenWithShadeExpanded() {
shadeRepository.setLockscreenShadeExpansion(1f)
shadeRepository.setQsExpansion(0f)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
new file mode 100644
index 0000000..183a58a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.policy
+
+import android.app.Notification
+import android.platform.test.annotations.EnableFlags
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.settings.FakeGlobalSettings
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.invocation.InvocationOnMock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@SmallTest
+@RunWithLooper
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(NotificationThrottleHun.FLAG_NAME)
+class AvalancheControllerTest : SysuiTestCase() {
+
+ private val mAvalancheController = AvalancheController()
+
+ // For creating mocks
+ @get:Rule var rule: MockitoRule = MockitoJUnit.rule()
+ @Mock private val runnableMock: Runnable? = null
+
+ // For creating TestableHeadsUpManager
+ @Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null
+ private val mUiEventLoggerFake = UiEventLoggerFake()
+ private val mLogger = Mockito.spy(HeadsUpManagerLogger(logcatLogBuffer()))
+ private val mGlobalSettings = FakeGlobalSettings()
+ private val mSystemClock = FakeSystemClock()
+ private val mExecutor = FakeExecutor(mSystemClock)
+ private var testableHeadsUpManager: BaseHeadsUpManager? = null
+
+ @Before
+ fun setUp() {
+ // Use default non-a11y timeout
+ Mockito.`when`(
+ mAccessibilityMgr!!.getRecommendedTimeoutMillis(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt()
+ )
+ )
+ .then { i: InvocationOnMock -> i.getArgument(0) }
+
+ // Initialize TestableHeadsUpManager here instead of at declaration, when mocks will be null
+ testableHeadsUpManager =
+ TestableHeadsUpManager(
+ mContext,
+ mLogger,
+ mExecutor,
+ mGlobalSettings,
+ mSystemClock,
+ mAccessibilityMgr,
+ mUiEventLoggerFake,
+ mAvalancheController
+ )
+ }
+
+ private fun createHeadsUpEntry(id: Int): BaseHeadsUpManager.HeadsUpEntry {
+ val entry = testableHeadsUpManager!!.createHeadsUpEntry()
+
+ entry.setEntry(
+ NotificationEntryBuilder()
+ .setSbn(HeadsUpManagerTestUtil.createSbn(id, Notification.Builder(mContext, "")))
+ .build()
+ )
+ return entry
+ }
+
+ @Test
+ fun testUpdate_isShowing_runsRunnable() {
+ // Entry is showing
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = headsUpEntry
+
+ // Update
+ mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Runnable was run
+ Mockito.verify(runnableMock, Mockito.times(1)).run()
+ }
+
+ @Test
+ fun testUpdate_noneShowingAndNotNext_showNow() {
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+
+ // None showing
+ mAvalancheController.headsUpEntryShowing = null
+
+ // Entry is NOT next
+ mAvalancheController.clearNext()
+
+ // Update
+ mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Entry is showing now
+ Truth.assertThat(mAvalancheController.headsUpEntryShowing).isEqualTo(headsUpEntry)
+ }
+
+ @Test
+ fun testUpdate_isNext_addsRunnable() {
+ // Another entry is already showing
+ val otherShowingEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = otherShowingEntry
+
+ // Entry is next
+ val headsUpEntry = createHeadsUpEntry(id = 1)
+ mAvalancheController.addToNext(headsUpEntry, runnableMock!!)
+
+ // Entry has one Runnable
+ val runnableList: List<Runnable?>? = mAvalancheController.nextMap[headsUpEntry]
+ Truth.assertThat(runnableList).isNotNull()
+ Truth.assertThat(runnableList!!.size).isEqualTo(1)
+
+ // Update
+ mAvalancheController.update(headsUpEntry, runnableMock, "testLabel")
+
+ // Entry has two Runnables
+ Truth.assertThat(runnableList.size).isEqualTo(2)
+ }
+
+ @Test
+ fun testUpdate_isNotNextWithOtherHunShowing_isNext() {
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+
+ // Another entry is already showing
+ val otherShowingEntry = createHeadsUpEntry(id = 1)
+ mAvalancheController.headsUpEntryShowing = otherShowingEntry
+
+ // Entry is NOT next
+ mAvalancheController.clearNext()
+
+ // Update
+ mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Entry is next
+ Truth.assertThat(mAvalancheController.nextMap.containsKey(headsUpEntry)).isTrue()
+ }
+
+ @Test
+ fun testDelete_isNext_removedFromNext_runnableNotRun() {
+ // Entry is next
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.addToNext(headsUpEntry, runnableMock!!)
+
+ // Delete
+ mAvalancheController.delete(headsUpEntry, runnableMock, "testLabel")
+
+ // Entry was removed from next
+ Truth.assertThat(mAvalancheController.nextMap.containsKey(headsUpEntry)).isFalse()
+
+ // Runnable was not run
+ Mockito.verify(runnableMock, Mockito.times(0)).run()
+ }
+
+ @Test
+ fun testDelete_wasDropped_removedFromDropSet() {
+ // Entry was dropped
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.debugDropSet.add(headsUpEntry)
+
+ // Delete
+ mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Entry was removed from dropSet
+ Truth.assertThat(mAvalancheController.debugDropSet.contains(headsUpEntry)).isFalse()
+ }
+
+ @Test
+ fun testDelete_wasDropped_runnableNotRun() {
+ // Entry was dropped
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.debugDropSet.add(headsUpEntry)
+
+ // Delete
+ mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Runnable was not run
+ Mockito.verify(runnableMock, Mockito.times(0)).run()
+ }
+
+ @Test
+ fun testDelete_isShowing_runnableRun() {
+ // Entry is showing
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = headsUpEntry
+
+ // Delete
+ mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Runnable was run
+ Mockito.verify(runnableMock, Mockito.times(1)).run()
+ }
+
+ @Test
+ fun testDelete_isShowing_showNext() {
+ // Entry is showing
+ val showingEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = showingEntry
+
+ // There's another entry waiting to show next
+ val nextEntry = createHeadsUpEntry(id = 1)
+ mAvalancheController.addToNext(nextEntry, runnableMock!!)
+
+ // Delete
+ mAvalancheController.delete(showingEntry, runnableMock, "testLabel")
+
+ // Next entry is shown
+ Truth.assertThat(mAvalancheController.headsUpEntryShowing).isEqualTo(nextEntry)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index db4d42f..830bcef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -35,13 +35,10 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.ActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Person;
import android.content.Intent;
-import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
import android.testing.TestableLooper;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -77,6 +74,8 @@
private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
+ private AvalancheController mAvalancheController = new AvalancheController();
+
@Mock private AccessibilityManagerWrapper mAccessibilityMgr;
protected static final int TEST_MINIMUM_DISPLAY_TIME = 400;
@@ -99,7 +98,7 @@
private BaseHeadsUpManager createHeadsUpManager() {
return new TestableHeadsUpManager(mContext, mLogger, mExecutor, mGlobalSettings,
- mSystemClock, mAccessibilityMgr, mUiEventLoggerFake);
+ mSystemClock, mAccessibilityMgr, mUiEventLoggerFake, mAvalancheController);
}
private NotificationEntry createStickyEntry(int id) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
index c032d7c..61a79d8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
@@ -28,8 +28,8 @@
import android.content.Context;
import android.testing.TestableLooper;
-import androidx.test.filters.SmallTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -76,6 +76,7 @@
@Mock private UiEventLogger mUiEventLogger;
@Mock private JavaAdapter mJavaAdapter;
@Mock private ShadeInteractor mShadeInteractor;
+ private AvalancheController mAvalancheController = new AvalancheController();
private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone {
TestableHeadsUpManagerPhone(
@@ -92,7 +93,8 @@
AccessibilityManagerWrapper accessibilityManagerWrapper,
UiEventLogger uiEventLogger,
JavaAdapter javaAdapter,
- ShadeInteractor shadeInteractor
+ ShadeInteractor shadeInteractor,
+ AvalancheController avalancheController
) {
super(
context,
@@ -109,7 +111,8 @@
accessibilityManagerWrapper,
uiEventLogger,
javaAdapter,
- shadeInteractor
+ shadeInteractor,
+ avalancheController
);
mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
@@ -131,12 +134,15 @@
mAccessibilityManagerWrapper,
mUiEventLogger,
mJavaAdapter,
- mShadeInteractor
+ mShadeInteractor,
+ mAvalancheController
);
}
@Before
public void setUp() {
+ // TODO(b/315362456) create separate test with the flag disabled
+ // then modify this file to test with the flag enabled
mSetFlagsRule.disableFlags(NotificationThrottleHun.FLAG_NAME);
when(mShadeInteractor.isAnyExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false));
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
index 2747629..d8f77f0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
@@ -43,9 +43,10 @@
GlobalSettings globalSettings,
SystemClock systemClock,
AccessibilityManagerWrapper accessibilityManagerWrapper,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ AvalancheController avalancheController) {
super(context, logger, mockExecutorHandler(executor), globalSettings, systemClock,
- executor, accessibilityManagerWrapper, uiEventLogger);
+ executor, accessibilityManagerWrapper, uiEventLogger, avalancheController);
mTouchAcceptanceDelay = BaseHeadsUpManagerTest.TEST_TOUCH_ACCEPTANCE_TIME;
mMinimumDisplayTime = BaseHeadsUpManagerTest.TEST_MINIMUM_DISPLAY_TIME;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
index a2f3ccb..e06efe8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
@@ -113,6 +113,28 @@
}
@Test
+ fun zenMuted_cantChange() {
+ with(kosmos) {
+ testScope.runTest {
+ notificationsSoundPolicyRepository.updateNotificationPolicy()
+ notificationsSoundPolicyRepository.updateZenMode(
+ ZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS)
+ )
+
+ val canChangeVolume by
+ collectLastValue(
+ underTest.canChangeVolume(AudioStream(AudioManager.STREAM_NOTIFICATION))
+ )
+
+ underTest.setMuted(AudioStream(AudioManager.STREAM_RING), true)
+ runCurrent()
+
+ assertThat(canChangeVolume).isFalse()
+ }
+ }
+ }
+
+ @Test
fun streamIsMuted_getStream_volumeZero() {
with(kosmos) {
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
index 36be90e..449e8bf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
@@ -78,7 +78,7 @@
with(kosmos) {
testScope.runTest {
localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
- spatializerRepository.setIsHeadTrackingAvailable(false)
+ spatializerRepository.defaultHeadTrackingAvailable = false
spatializerRepository.defaultSpatialAudioAvailable = false
val isAvailable by collectLastValue(underTest.isAvailable())
@@ -94,7 +94,7 @@
with(kosmos) {
testScope.runTest {
localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
- spatializerRepository.setIsHeadTrackingAvailable(false)
+ spatializerRepository.defaultHeadTrackingAvailable = false
spatializerRepository.defaultSpatialAudioAvailable = true
val isAvailable by collectLastValue(underTest.isAvailable())
@@ -110,7 +110,7 @@
with(kosmos) {
testScope.runTest {
localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
- spatializerRepository.setIsHeadTrackingAvailable(true)
+ spatializerRepository.defaultHeadTrackingAvailable = true
spatializerRepository.defaultSpatialAudioAvailable = true
val isAvailable by collectLastValue(underTest.isAvailable())
@@ -125,7 +125,7 @@
fun spatialAudio_headTracking_noDevice_unavailable() {
with(kosmos) {
testScope.runTest {
- spatializerRepository.setIsHeadTrackingAvailable(true)
+ spatializerRepository.defaultHeadTrackingAvailable = true
spatializerRepository.defaultSpatialAudioAvailable = true
val isAvailable by collectLastValue(underTest.isAvailable())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
index eb6f0b2..06ae220 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
@@ -82,7 +82,7 @@
),
true
)
- spatializerRepository.setIsHeadTrackingAvailable(true)
+ spatializerRepository.defaultHeadTrackingAvailable = true
underTest =
SpatialAudioComponentInteractor(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
index 71866b3..82ce6d7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
@@ -37,7 +37,7 @@
DefaultComponentsLayoutManager(
BOTTOM_BAR,
headerComponents = listOf(COMPONENT_1),
- footerComponents = listOf(COMPONENT_2),
+ footerComponents = listOf(COMPONENT_5, COMPONENT_2),
)
@Test
@@ -48,10 +48,18 @@
val component2 = ComponentState(COMPONENT_2, kosmos.mockVolumePanelUiComponent, false)
val component3 = ComponentState(COMPONENT_3, kosmos.mockVolumePanelUiComponent, false)
val component4 = ComponentState(COMPONENT_4, kosmos.mockVolumePanelUiComponent, false)
+ val component5 = ComponentState(COMPONENT_5, kosmos.mockVolumePanelUiComponent, false)
val layout =
underTest.layout(
VolumePanelState(0, false, false),
- setOf(bottomBarComponentState, component1, component2, component3, component4)
+ setOf(
+ bottomBarComponentState,
+ component1,
+ component2,
+ component3,
+ component4,
+ component5,
+ )
)
Truth.assertThat(layout.bottomBarComponent).isEqualTo(bottomBarComponentState)
@@ -59,7 +67,7 @@
.containsExactlyElementsIn(listOf(component1))
.inOrder()
Truth.assertThat(layout.footerComponents)
- .containsExactlyElementsIn(listOf(component2))
+ .containsExactlyElementsIn(listOf(component5, component2))
.inOrder()
Truth.assertThat(layout.contentComponents)
.containsExactlyElementsIn(listOf(component3, component4))
@@ -85,5 +93,6 @@
const val COMPONENT_2: VolumePanelComponentKey = "test_component:2"
const val COMPONENT_3: VolumePanelComponentKey = "test_component:3"
const val COMPONENT_4: VolumePanelComponentKey = "test_component:4"
+ const val COMPONENT_5: VolumePanelComponentKey = "test_component:5"
}
}
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index f51e109..7341015 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -304,6 +304,15 @@
<!-- An explanation text that the password needs to be entered since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] -->
<string name="kg_prompt_reason_timeout_password">For additional security, use password instead</string>
+ <!-- An explanation text that the pin needs to be provided to enter the device for security reasons. [CHAR LIMIT=70] -->
+ <string name="kg_prompt_added_security_pin">PIN required for additional security</string>
+
+ <!-- An explanation text that the pattern needs to be provided to enter the device for security reasons. [CHAR LIMIT=70] -->
+ <string name="kg_prompt_added_security_pattern">Pattern required for additional security</string>
+
+ <!-- An explanation text that the password needs to be provided to enter the device for security reasons. [CHAR LIMIT=70] -->
+ <string name="kg_prompt_added_security_password">Password required for additional security</string>
+
<!-- An explanation text that the credential needs to be entered because a device admin has
locked the device. [CHAR LIMIT=80] -->
<string name="kg_prompt_reason_device_admin">Device locked by admin</string>
diff --git a/packages/SystemUI/res/drawable/ic_head_tracking.xml b/packages/SystemUI/res/drawable/ic_head_tracking.xml
new file mode 100644
index 0000000..d4a44fd
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_head_tracking.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M480,520Q414,520 367,473Q320,426 320,360Q320,294 367,247Q414,200 480,200Q546,200 593,247Q640,294 640,360Q640,426 593,473Q546,520 480,520ZM160,840L160,728Q160,695 177,666Q194,637 224,622Q275,596 339,578Q403,560 480,560Q557,560 621,578Q685,596 736,622Q766,637 783,666Q800,695 800,728L800,840L160,840ZM240,760L720,760L720,728Q720,717 714.5,708Q709,699 700,694Q664,676 607.5,658Q551,640 480,640Q409,640 352.5,658Q296,676 260,694Q251,699 245.5,708Q240,717 240,728L240,760ZM480,440Q513,440 536.5,416.5Q560,393 560,360Q560,327 536.5,303.5Q513,280 480,280Q447,280 423.5,303.5Q400,327 400,360Q400,393 423.5,416.5Q447,440 480,440ZM39,200L39,120Q56,120 70,113.5Q84,107 95,96Q106,85 112,71Q118,57 118,40L199,40Q199,73 186.5,102Q174,131 152,153Q130,175 101,187.5Q72,200 39,200ZM39,361L39,281Q90,281 133.5,262Q177,243 209,210Q241,177 260,133.5Q279,90 279,40L360,40Q360,106 335,164.5Q310,223 266,267Q222,311 164,336Q106,361 39,361ZM920,361Q854,361 795.5,336Q737,311 693,267Q649,223 624,164.5Q599,106 599,40L679,40Q679,90 698,133.5Q717,177 750,210Q783,243 826.5,262Q870,281 920,281L920,361ZM920,200Q887,200 858,187.5Q829,175 807,153Q785,131 772.5,102Q760,73 760,40L840,40Q840,57 846.5,71Q853,85 864,96Q875,107 889,113.5Q903,120 920,120L920,200ZM480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360ZM480,760L480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760L480,760L480,760Z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_spatial_audio.xml b/packages/SystemUI/res/drawable/ic_spatial_audio.xml
new file mode 100644
index 0000000..0ee609a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_spatial_audio.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M920,401Q848,401 782,373.5Q716,346 665,295Q614,244 586.5,178Q559,112 559,40L639,40Q639,97 660,148Q681,199 721,239Q761,279 812,300.5Q863,322 920,322L920,401ZM920,242Q879,242 842.5,227Q806,212 777,183Q748,154 733,117.5Q718,81 718,40L797,40Q797,65 806.5,87.5Q816,110 833,127Q850,144 872.5,153Q895,162 920,162L920,242ZM400,520Q334,520 287,473Q240,426 240,360Q240,294 287,247Q334,200 400,200Q466,200 513,247Q560,294 560,360Q560,426 513,473Q466,520 400,520ZM80,840L80,728Q80,695 97,666Q114,637 144,622Q195,596 259,578Q323,560 400,560Q477,560 541,578Q605,596 656,622Q686,637 703,666Q720,695 720,728L720,840L80,840ZM160,760L640,760L640,728Q640,717 634.5,708Q629,699 620,694Q584,676 527.5,658Q471,640 400,640Q329,640 272.5,658Q216,676 180,694Q171,699 165.5,708Q160,717 160,728L160,760ZM400,440Q433,440 456.5,416.5Q480,393 480,360Q480,327 456.5,303.5Q433,280 400,280Q367,280 343.5,303.5Q320,327 320,360Q320,393 343.5,416.5Q367,440 400,440ZM400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360ZM400,760L400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760L400,760L400,760Z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_spatial_audio_off.xml b/packages/SystemUI/res/drawable/ic_spatial_audio_off.xml
new file mode 100644
index 0000000..c7d3272
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_spatial_audio_off.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M750,550L806,494Q766,454 743.5,402.5Q721,351 721,294Q721,237 743.5,186Q766,135 806,95L750,37Q699,88 670,155Q641,222 641,294Q641,366 670,432.5Q699,499 750,550ZM862,436L918,380Q901,363 891,341Q881,319 881,294Q881,269 891,247Q901,225 918,208L862,151Q833,180 817,216Q801,252 801,293Q801,334 817,371Q833,408 862,436ZM400,520Q334,520 287,473Q240,426 240,360Q240,294 287,247Q334,200 400,200Q466,200 513,247Q560,294 560,360Q560,426 513,473Q466,520 400,520ZM80,840L80,728Q80,695 97,666Q114,637 144,622Q195,596 259,578Q323,560 400,560Q477,560 541,578Q605,596 656,622Q686,637 703,666Q720,695 720,728L720,840L80,840ZM160,760L640,760L640,728Q640,717 634.5,708Q629,699 620,694Q584,676 527.5,658Q471,640 400,640Q329,640 272.5,658Q216,676 180,694Q171,699 165.5,708Q160,717 160,728L160,760ZM400,440Q433,440 456.5,416.5Q480,393 480,360Q480,327 456.5,303.5Q433,280 400,280Q367,280 343.5,303.5Q320,327 320,360Q320,393 343.5,416.5Q367,440 400,440ZM400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360ZM400,760L400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760L400,760L400,760Z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml b/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml
new file mode 100644
index 0000000..cf7a730
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+ <!-- Since this layer list has just one layer, we can remove it and replace with the inner
+ layer drawable. However this should only be done when the flag
+ com.android.systemui.qs_tile_focus_state has completed all its stages and this drawable
+ fully replaces the previous one to ensure consistency with code sections searching for
+ specific ids in drawable hierarchy -->
+ <item
+ android:id="@id/background">
+ <layer-list>
+ <item
+ android:id="@+id/qs_tile_background_base"
+ android:drawable="@drawable/qs_tile_background_shape" />
+ <item android:id="@+id/qs_tile_background_overlay">
+ <selector>
+ <item
+ android:state_hovered="true"
+ android:drawable="@drawable/qs_tile_background_shape" />
+ </selector>
+ </item>
+ <!-- In the layer below we have negative insets because we need the focus outline
+ to draw outside the bounds, around the main background. We use 5dp because
+ the outline stroke is 3dp and the required padding is 2dp.-->
+ <item
+ android:top="-5dp"
+ android:right="-5dp"
+ android:left="-5dp"
+ android:bottom="-5dp">
+ <selector>
+ <item
+ android:state_focused="true"
+ android:drawable="@drawable/qs_tile_focused_background"/>
+ </selector>
+ </item>
+ </layer-list>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_tile_focused_background.xml b/packages/SystemUI/res/drawable/qs_tile_focused_background.xml
new file mode 100644
index 0000000..fd456df
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_tile_focused_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <corners android:radius="30dp"/>
+ <stroke android:width="3dp" android:color="?androidprv:attr/materialColorSecondaryFixed"/>
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
index ac781ec..13355f3 100644
--- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
@@ -181,12 +181,11 @@
<TextView
android:id="@+id/bluetooth_auto_on_toggle_info_text"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:paddingStart="36dp"
android:paddingEnd="40dp"
- android:text="@string/turn_on_bluetooth_auto_info"
android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 515ef61..2e2d286 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Skakel aan"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Skakel aan"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Nee, dankie"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standaard"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Ekstreem"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Outodraai skerm"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Gee <xliff:g id="APPLICATION">%1$s</xliff:g> toegang tot <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Laat <xliff:g id="APPLICATION">%1$s</xliff:g> toe om by <xliff:g id="USB_DEVICE">%2$s</xliff:g> in te gaan?\nOpneemtoestemming is nie aan hierdie program verleen nie, maar dit kan oudio deur hierdie USB-toestel vasvang."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Skakel dit môre outomaties weer aan"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Kenmerke soos Kitsdeel, Kry My Toestel en toestelligging gebruik Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Voeg meer legstukke by"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Langdruk om legstukke te pasmaak"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Pasmaak legstukke"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App-ikoon vir gedeaktiveerde legstuk"</string>
<string name="edit_widget" msgid="9030848101135393954">"Wysig legstuk"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Verwyder"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Voeg legstuk by"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Tik om op vibreer te stel."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Tik om te demp."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Geraasbeheer"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Tik om luiermodus te verander"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"demp"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"ontdemp"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Aan/af-kieslys"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Bladsy <xliff:g id="ID_1">%1$d</xliff:g> van <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Sluitskerm"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Jy kan hierdie foon met Kry My Toestel opspoor selfs wanneer dit afgeskakel is"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Sit tans af …"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Sien versorgingstappe"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Sien versorgingstappe"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Prop jou toestel uit"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Keer die foon om vir hoër resolusie"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Voubare toestel word ontvou"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Voubare toestel word omgekeer"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"gevou"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"oopgevou"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> batterykrag oor"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Koppel jou stilus aan ’n laaier"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Stilus se battery is amper pap"</string>
diff --git a/packages/SystemUI/res/values-af/tiles_states_strings.xml b/packages/SystemUI/res/values-af/tiles_states_strings.xml
index 1c9a7941..1427574 100644
--- a/packages/SystemUI/res/values-af/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-af/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Af"</item>
<item msgid="578444932039713369">"Aan"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Onbeskikbaar"</item>
+ <item msgid="9061144428113385092">"Af"</item>
+ <item msgid="2984256114867200368">"Aan"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Onbeskikbaar"</item>
<item msgid="8707481475312432575">"Af"</item>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 9763ff2..897820c 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"አብራ"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"አብራ"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"አይ፣ አመሰግናለሁ"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"መደበኛ"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"እጅግ ከፍተኛ"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"ማያ በራስ ሰር አሽከርክር"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> <xliff:g id="USB_DEVICE">%2$s</xliff:g>ን እንዲደርስበት ይፈቀድለት?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g> <xliff:g id="USB_DEVICE">%2$s</xliff:g>ን እንዲደርስ ይፈቀድለት?\nይህ መተግበሪያ የመቅዳት ፈቃድ አልተሰጠውም፣ ነገር ግን በዩኤስቢ መሣሪያ በኩል ኦዲዮን መቅዳት ይችላል።"</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"ነገ እንደገና በራስ-ሰር አስጀምር"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"እንደ ፈጣን ማጋራት፣ የእኔን መሣሪያ አግኝ እና የመሣሪያ አካባቢ ያሉ ባህሪያት ብሉቱዝን ይጠቀማሉ"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"ተጨማሪ ምግብሮችን ያክሉ"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ምግብሮችን ለማበጀት በረጅሙ ይጫኑ"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ምግብሮችን አብጅ"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"የመተግበሪያ አዶ ለተሰናከለ ምግብር"</string>
<string name="edit_widget" msgid="9030848101135393954">"ምግብርን አርትዕ"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"አስወግድ"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ምግብር አክል"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s። ወደ ንዝረት ለማቀናበር መታ ያድርጉ።"</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s። ድምጸ-ከል ለማድረግ መታ ያድርጉ።"</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"የጫጫታ መቆጣጠሪያ"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"የደዋይ ሁነታን ለመቀየር መታ ያድርጉ"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"ድምጸ-ከል አድርግ"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"ድምጸ-ከልን አንሳ"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"የኃይል ምናሌ"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"ገፅ <xliff:g id="ID_1">%1$d</xliff:g> ከ <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"ማያ ገፅ ቁልፍ"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"ይህ መሣሪያ ኃይል ጠፍቶ ቢሆንም እንኳን በየእኔን መሣሪያ አግኝ ማግኘት ይችላሉ"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"በመዝጋት ላይ…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"የእንክብካቤ ደረጃዎችን ይመልከቱ"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"የእንክብካቤ ደረጃዎችን ይመልከቱ"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"መሣሪያዎን ይንቀሉ"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ለከፍተኛ ጥራት ስልኩን ይቀይሩ"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"መታጠፍ የሚችል መሣሪያ እየተዘረጋ ነው"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"መታጠፍ የሚችል መሣሪያ እየተገለበጠ ነው"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"የታጠፈ"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"የተዘረጋ"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ባትሪ ይቀራል"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ብሮስፌዎን ከኃይል መሙያ ጋር ያገናኙ"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"የብሮስፌ ባትሪ ዝቅተኛ ነው"</string>
diff --git a/packages/SystemUI/res/values-am/tiles_states_strings.xml b/packages/SystemUI/res/values-am/tiles_states_strings.xml
index 3fb24b9..ab0b68b 100644
--- a/packages/SystemUI/res/values-am/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-am/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"ጠፍቷል"</item>
<item msgid="578444932039713369">"በርቷል"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"አይገኝም"</item>
+ <item msgid="9061144428113385092">"አጥፋ"</item>
+ <item msgid="2984256114867200368">"አብራ"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"አይገኝም"</item>
<item msgid="8707481475312432575">"ጠፍቷል"</item>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 1b7e303..89b4ccf 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"تفعيل"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"تفعيل"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"لا، شكرًا"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"عادي"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"التوفير العالي"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"التدوير التلقائي للشاشة"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"هل تريد السماح لتطبيق <xliff:g id="APPLICATION">%1$s</xliff:g> بالدخول إلى <xliff:g id="USB_DEVICE">%2$s</xliff:g>؟"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"هل تريد السماح لتطبيق <xliff:g id="APPLICATION">%1$s</xliff:g> بالدخول إلى <xliff:g id="USB_DEVICE">%2$s</xliff:g>؟\nلم يتم منح هذا التطبيق إذن تسجيل، ولكن يمكنه تسجيل الصوت من خلال جهاز USB هذا."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"تفعيل البلوتوث تلقائيًا مرة أخرى غدًا"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"يُستخدَم البلوتوث في ميزات مثل Quick Share و\"العثور على جهازي\" والموقع الجغرافي للجهاز"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"إضافة المزيد من التطبيقات المصغّرة"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"اضغط مع الاستمرار لتخصيص التطبيقات المصغّرة."</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"تخصيص التطبيقات المصغَّرة"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"رمز التطبيق المصغّر غير المفعّل"</string>
<string name="edit_widget" msgid="9030848101135393954">"تعديل التطبيق المصغَّر"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"إزالة"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"إضافة تطبيق مصغّر"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. انقر للتعيين على الاهتزاز."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. انقر لكتم الصوت."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"التحكُّم في مستوى الضجيج"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"انقر لتغيير وضع الرنين."</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"كتم الصوت"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"إعادة الصوت"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"قائمة زر التشغيل"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"الصفحة <xliff:g id="ID_1">%1$d</xliff:g> من <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"شاشة القفل"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"يمكنك تحديد مكان هذا الهاتف باستخدام تطبيق \"العثور على جهازي\" حتى عندما يكون مُطفئًا."</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"جارٍ إيقاف التشغيل…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"الاطّلاع على خطوات العناية"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"الاطّلاع على خطوات العناية"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"افصِل جهازك"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"للحصول على درجة دقة أعلى، اقلِب الهاتف."</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"جهاز قابل للطي يجري فتحه"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"جهاز قابل للطي يجري قلبه"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"مطوي"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"غير مطوي"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"النسبة المئوية المتبقية من شحن البطارية: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"عليك توصيل قلم الشاشة بشاحن."</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"بطارية قلم الشاشة منخفضة"</string>
diff --git a/packages/SystemUI/res/values-ar/tiles_states_strings.xml b/packages/SystemUI/res/values-ar/tiles_states_strings.xml
index cf050ac..364737d 100644
--- a/packages/SystemUI/res/values-ar/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-ar/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"الميزة غير مفعّلة"</item>
<item msgid="578444932039713369">"الميزة مفعّلة"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"غير متوفّر"</item>
+ <item msgid="9061144428113385092">"غير مفعَّل"</item>
+ <item msgid="2984256114867200368">"مفعَّل"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"الميزة غير متاحة"</item>
<item msgid="8707481475312432575">"الميزة غير مفعّلة"</item>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 429f03e..9a2d744 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"অন কৰক"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"অন কৰক"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"নালাগে, ধন্যবাদ"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"মানক"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"চৰম"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"স্বয়ং-ঘূৰ্ণন স্ক্ৰীন"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> এক্সেছ কৰিবলৈ <xliff:g id="APPLICATION">%1$s</xliff:g>ক অনুমতি দিবনে?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g>ক <xliff:g id="USB_DEVICE">%2$s</xliff:g> এক্সেছ কৰিবলৈ অনুমতি দিবনে?\nএই এপ্টোক ৰেকর্ড কৰাৰ অনুমতি দিয়া হোৱা নাই কিন্তু ই এই ইউএছবি ডিভাইচটোৰ জৰিয়তে অডিঅ\' ৰেকর্ড কৰিব পাৰে।"</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"কাইলৈ পুনৰ স্বয়ংক্ৰিয়ভাৱে অন কৰক"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Quick Share, Find My Device আৰু ডিভাইচৰ অৱস্থানৰ দৰে সুবিধাই ব্লুটুথ ব্যৱহাৰ কৰে"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"অধিক ৱিজেট যোগ দিয়ক"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ৱিজেট কাষ্টমাইজ কৰিবলৈ দীঘলীয়াকৈ টিপক"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ৱিজেট কাষ্টমাইজ কৰক"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"অক্ষম কৰা ৱিজেটৰ বাবে এপৰ চিহ্ন"</string>
<string name="edit_widget" msgid="9030848101135393954">"ৱিজেট সম্পাদনা কৰক"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"আঁতৰাওক"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ৱিজেট যোগ দিয়ক"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s। কম্পন অৱস্থাত ছেট কৰিবলৈ টিপক।"</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s। মিউট কৰিবলৈ টিপক।"</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"কোলাহল নিয়ন্ত্ৰণ"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"ৰিংগাৰ ম’ড সলনি কৰিবলৈ টিপক"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"মিউট কৰক"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"আনমিউট কৰক"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"পাৱাৰ মেনু"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g>ৰ পৃষ্ঠা <xliff:g id="ID_1">%1$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"লক স্ক্ৰীন"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"পাৱাৰ অফ কৰা থাকিলেও Find My Deviceৰ জৰিয়তে আপুনি এই ফ’নটোৰ অৱস্থান নিৰ্ধাৰণ কৰিব পাৰে"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"বন্ধ কৰি থকা হৈছে…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"যত্ন লোৱাৰ পদক্ষেপসমূহ চাওক"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"যত্ন লোৱাৰ পদক্ষেপসমূহ চাওক"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"আপোনাৰ ডিভাইচটো আনপ্লাগ কৰক"</string>
@@ -1226,6 +1236,12 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"অধিক ৰিজ’লিউছনৰ বাবে, ফ’নটো লুটিয়াই দিয়ক"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"জপাব পৰা ডিভাইচৰ জাপ খুলি থকা হৈছে"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"জপাব পৰা ডিভাইচৰ ওলোটাই থকা হৈছে"</string>
+ <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+ <skip />
+ <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+ <skip />
+ <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+ <skip />
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> বেটাৰী বাকী আছে"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"আপোনাৰ ষ্টাইলাছ এটা চাৰ্জাৰৰ সৈতে সংযোগ কৰক"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"ষ্টাইলাছৰ বেটাৰী কম আছে"</string>
diff --git a/packages/SystemUI/res/values-as/tiles_states_strings.xml b/packages/SystemUI/res/values-as/tiles_states_strings.xml
index f4268ed..767b34d 100644
--- a/packages/SystemUI/res/values-as/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-as/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"অফ আছে"</item>
<item msgid="578444932039713369">"অন কৰা আছে"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"উপলব্ধ নহয়"</item>
+ <item msgid="9061144428113385092">"অফ আছে"</item>
+ <item msgid="2984256114867200368">"অন আছে"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"উপলব্ধ নহয়"</item>
<item msgid="8707481475312432575">"অফ আছে"</item>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 639cbbc..ebd4073 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Aktivləşdirin"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Aktiv edin"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Xeyr, təşəkkür"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standart"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Ekstremal"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Ekranın avtomatik dönməsi"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> tətbiqinə <xliff:g id="USB_DEVICE">%2$s</xliff:g> cihazına giriş icazəsi verilsin?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g> tətbiqinə <xliff:g id="USB_DEVICE">%2$s</xliff:g> cihazına giriş verilsin?\nTətbiqə qeydə almaq icazəsi verilməsə də, bu USB vasitəsilə səsi qeydə ala bilər."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Sabah avtomatik aktiv edin"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Cəld Paylaşım, Cihazın Tapılması və cihaz məkanı kimi funksiyalar Bluetooth istifadə edir"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Vidcetlər əlavə edin"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Basıb saxlayaraq vidcetləri fərdiləşdirin"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Vidcetləri fərdiləşdirin"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Deaktiv edilmiş vidcet üçün tətbiq ikonası"</string>
<string name="edit_widget" msgid="9030848101135393954">"Vidceti redaktə edin"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Silin"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Vidcet əlavə edin"</string>
@@ -582,7 +586,15 @@
<string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Səssiz etmək üçün tıklayın. Əlçatımlılıq xidmətləri səssiz edilmiş ola bilər."</string>
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Vibrasiyanı ayarlamaq üçün klikləyin."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Səssiz etmək üçün klikləyin."</string>
- <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Səs-küy idarəsi"</string>
+ <string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Səs-küy idarəetməsi"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Zəng rejimini dəyişmək üçün toxunun"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"susdurun"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"səssiz rejimdən çıxarın"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Qidalanma düyməsi menyusu"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g> səhifədən <xliff:g id="ID_1">%1$d</xliff:g> səhifə"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Ekran kilidi"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Bu telefon sönülü olsa belə, Cihazın Tapılması ilə onu tapa bilərsiniz"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Söndürülür…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Ehtiyat tədbiri mərhələlərinə baxın"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ehtiyat tədbiri mərhələlərinə baxın"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Cihazınızı ayırın"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Daha yüksək ayırdetmə dəqiqliyi üçün telefonu çevirin"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Qatlana bilən cihaz açılır"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Qatlana bilən cihaz fırladılır"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"qatlanmış"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"açıq"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> enerji qalıb"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Qələmi adapterə qoşun"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Qələm enerjisi azdır"</string>
diff --git a/packages/SystemUI/res/values-az/tiles_states_strings.xml b/packages/SystemUI/res/values-az/tiles_states_strings.xml
index eeb81cc..3457a71 100644
--- a/packages/SystemUI/res/values-az/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-az/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Deaktiv"</item>
<item msgid="578444932039713369">"Aktiv"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Əlçatan deyil"</item>
+ <item msgid="9061144428113385092">"Deaktiv"</item>
+ <item msgid="2984256114867200368">"Aktiv"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Əlçatan deyil"</item>
<item msgid="8707481475312432575">"Deaktiv"</item>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index e97dbec..17599b2 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Uključi"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Uključi"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Ne, hvala"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standardno"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Ekstremno"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Automatsko rotiranje ekrana"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Dozvoljavate da <xliff:g id="APPLICATION">%1$s</xliff:g> pristupa uređaju <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Želite li da dozvolite da <xliff:g id="APPLICATION">%1$s</xliff:g> pristupa uređaju <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nOva aplikacija nema dozvolu za snimanje, ali bi mogla da snima zvuk pomoću ovog USB uređaja."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatski ponovo uključi sutra"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funkcije kao što su Quick Share, Pronađi moj uređaj i lokacija uređaja koriste Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Dodajte još vidžeta"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Dugi pritisak za prilagođavanje vidžeta"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Prilagodi vidžete"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikacije za onemogućen vidžet"</string>
<string name="edit_widget" msgid="9030848101135393954">"Izmeni vidžet"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Ukloni"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodaj vidžet"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Dodirnite da biste podesili na vibraciju."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Dodirnite da biste isključili zvuk."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Kontrola šuma"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Dodirnite da biste promenili režim zvona"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"isključite zvuk"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"uključite zvuk"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Meni dugmeta za uključivanje"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_1">%1$d</xliff:g>. strana od <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Zaključan ekran"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Možete da locirate ovaj telefon pomoću usluge Pronađi moj uređaj čak i kada je isključen"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Isključuje se…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Pogledajte upozorenja"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Pogledajte upozorenja"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Isključite uređaj"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Za veću rezoluciju obrnite telefon"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Uređaj na preklop se otvara"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Uređaj na preklop se obrće"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"zatvoreno"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"otvoreno"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Preostalo je još<xliff:g id="PERCENTAGE">%s</xliff:g> baterije"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Povežite pisaljku sa punjačem"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Nizak nivo baterije pisaljke"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/tiles_states_strings.xml b/packages/SystemUI/res/values-b+sr+Latn/tiles_states_strings.xml
index 217d999..75fb325 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Isključeno"</item>
<item msgid="578444932039713369">"Uključeno"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Nedostupno"</item>
+ <item msgid="9061144428113385092">"Isključeno"</item>
+ <item msgid="2984256114867200368">"Uključeno"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Nedostupno"</item>
<item msgid="8707481475312432575">"Isključeno"</item>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 3b06d05..93921fc 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Уключыць"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Уключыць"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Не, дзякуй"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Стандартны рэжым"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Максімальная"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Аўтаматычны паварот экрана"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Дазволіць праграме <xliff:g id="APPLICATION">%1$s</xliff:g> доступ да прылады <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Даць праграме \"<xliff:g id="APPLICATION">%1$s</xliff:g>\" доступ да прылады \"<xliff:g id="USB_DEVICE">%2$s</xliff:g>\"?\nУ гэтай праграмы няма дазволу на запіс, аднак яна зможа запісваць аўдыя праз гэту прыладу USB."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Аўтаматычнае ўключэнне заўтра"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Такія функцыі, як вызначэнне месцазнаходжання прылады, Хуткае абагульванне і Знайсці прыладу, выкарыстоўваюць Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Дадаць іншыя віджэты"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Доўга націскайце, каб наладзіць віджэты"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Наладзіць віджэты"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Значок праграмы для адключанага віджэта"</string>
<string name="edit_widget" msgid="9030848101135393954">"Змяніць віджэт"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Выдаліць"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Дадаць віджэт"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Дакраніцеся, каб уключыць вібрацыю."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Дакраніцеся, каб адключыць гук"</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Кантроль шуму"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Націсніце, каб змяніць рэжым званка"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"выключыць гук"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"уключыць гук"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Меню кнопкі сілкавання"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Старонка <xliff:g id="ID_1">%1$d</xliff:g> з <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Экран блакіроўкі"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Вы можаце знайсці свой тэлефон з дапамогай праграмы \"Знайсці прыладу\", нават калі ён выключаны"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Ідзе завяршэнне працы…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Глядзець паэтапную дапамогу"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Глядзець паэтапную дапамогу"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Адключыце прыладу"</string>
@@ -1226,6 +1236,12 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Каб зрабіць фота з больш высокай раздзяляльнасцю, павярніце тэлефон"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Складная прылада ў раскладзеным выглядзе"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Перавернутая складная прылада"</string>
+ <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+ <skip />
+ <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+ <skip />
+ <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+ <skip />
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Засталося зараду: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Падключыце пяро да зараднай прылады"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Нізкі ўзровень зараду пяра"</string>
diff --git a/packages/SystemUI/res/values-be/tiles_states_strings.xml b/packages/SystemUI/res/values-be/tiles_states_strings.xml
index 717e4c9..74fc7c6 100644
--- a/packages/SystemUI/res/values-be/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-be/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Выключана"</item>
<item msgid="578444932039713369">"Уключана"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Недаступна"</item>
+ <item msgid="9061144428113385092">"Выключана"</item>
+ <item msgid="2984256114867200368">"Уключана"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Недаступна"</item>
<item msgid="8707481475312432575">"Выключана"</item>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 643ef9c..f3ae441 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Включване"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Включване"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Не, благодаря"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Стандартен"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Екстремен режим"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Авт. завъртане на екрана"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Да се разреши ли на <xliff:g id="APPLICATION">%1$s</xliff:g> достъп до <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Наистина ли искате да разрешите на <xliff:g id="APPLICATION">%1$s</xliff:g> достъп до <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nНа приложението не е предоставено разрешение за записване, но е възможно да запише звук чрез това USB устройство."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Автоматично включване отново утре"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Bluetooth се използва от различни функции, като например „Бързо споделяне“, „Намиране на устройството ми“ и местоположението на устройството"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Добавете още приспособления"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Натиснете продължително за персонализ. на приспос."</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Персонализиране на приспособленията"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Икона на приложение за деактивирано приспособление"</string>
<string name="edit_widget" msgid="9030848101135393954">"Редактиране на приспособлението"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Премахване"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Добавяне на приспособление"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Докоснете, за да зададете вибриране."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Докоснете, за да заглушите звука."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Управление на шума"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Докоснете, за да промените режима на звънене"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"спиране"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"пускане"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Меню за включване/изключване"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Страница <xliff:g id="ID_1">%1$d</xliff:g> от <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Заключен екран"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Можете да откриете този телефон посредством „Намиране на устройството ми“ дори когато е изключен"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Изключва се…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Вижте стъпките, които да предприемете"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Вижте стъпките, които да предприемете"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Изключете устройството си"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"За по-висока разделителна способност обърнете телефона"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Разгъване на сгъваемо устройство"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Обръщане на сгъваемо устройство"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"затворено"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"отворено"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Оставаща батерия: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Свържете писалката към зарядно устройство"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Батерията на писалката е изтощена"</string>
diff --git a/packages/SystemUI/res/values-bg/tiles_states_strings.xml b/packages/SystemUI/res/values-bg/tiles_states_strings.xml
index 58fa82b..ddd0c3f 100644
--- a/packages/SystemUI/res/values-bg/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-bg/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Изкл."</item>
<item msgid="578444932039713369">"Вкл."</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Не е налице"</item>
+ <item msgid="9061144428113385092">"Изкл."</item>
+ <item msgid="2984256114867200368">"Вкл."</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Не е налице"</item>
<item msgid="8707481475312432575">"Изкл."</item>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 9a2f040..0e95c2c 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"চালু করুন"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"চালু করুন"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"না থাক"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"সাধারণ"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"এক্সট্রিম"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"অটো-রোটেট স্ক্রিন"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> কে <xliff:g id="USB_DEVICE">%2$s</xliff:g> অ্যাক্সেস করতে দেবেন?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> অ্যাক্সেস করতে <xliff:g id="APPLICATION">%1$s</xliff:g>-কে কি অনুমতি দেবেন?\nএই অ্যাপকে রেকর্ড করার অনুমতি দেওয়া হয়নি কিন্তু USB ডিভাইসের মাধ্যমে সেটি অডিও রেকর্ড করতে পারে।"</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"আগামীকাল অটোমেটিক আবার চালু হবে"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"দ্রুত শেয়ার, Find My Device ও ডিভাইসের লোকেশন ব্লুটুথ ব্যবহার করে"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"আরও উইজেট যোগ করুন"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"উইজেট কাস্টমাইজ করতে বেশিক্ষণ প্রেস করুন"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"উইজেট কাস্টমাইজ করুন"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"বন্ধ করা উইজেটের জন্য অ্যাপের আইকন"</string>
<string name="edit_widget" msgid="9030848101135393954">"উইজেট এডিট করুন"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"সরান"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"উইজেট যোগ করুন"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s। ভাইব্রেট করতে ট্যাপ করুন।"</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s। মিউট করতে ট্যাপ করুন।"</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"আশপাশের আওয়াজ কন্ট্রোল করা"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"রিঙ্গার মোড পরিবর্তন করতে ট্যাপ করুন"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"মিউট করুন"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"আনমিউট করুন"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"পাওয়ার মেনু"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g>টির মধ্যে <xliff:g id="ID_1">%1$d</xliff:g> নং পৃষ্ঠা"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"লক স্ক্রিন"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Find My Device-এর মাধ্যমে, ফোনটি বন্ধ করা থাকলেও এটির লোকেশন শনাক্ত করতে পারবেন"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"বন্ধ হচ্ছে…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"ডিভাইস রক্ষণাবেক্ষণের ধাপগুলি দেখুন"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ডিভাইস রক্ষণাবেক্ষণের ধাপগুলি দেখুন"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"আপনার ডিভাইস আনপ্লাগ করা"</string>
@@ -1226,6 +1236,12 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"আরও বেশি রেজোলিউশনের জন্য, ফোন ফ্লিপ করুন"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ফোল্ড করা যায় এমন ডিভাইস খোলা হচ্ছে"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ফোল্ড করা যায় এমন ডিভাইস উল্টানো হচ্ছে"</string>
+ <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+ <skip />
+ <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+ <skip />
+ <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+ <skip />
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ব্যাটারির চার্জ বাকি আছে"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"কোনও চার্জারের সাথে আপনার স্টাইলাস কানেক্ট করুন"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"স্টাইলাস ব্যাটারিতে চার্জ কম আছে"</string>
diff --git a/packages/SystemUI/res/values-bn/tiles_states_strings.xml b/packages/SystemUI/res/values-bn/tiles_states_strings.xml
index 5c3c66c..ad32560 100644
--- a/packages/SystemUI/res/values-bn/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-bn/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"বন্ধ আছে"</item>
<item msgid="578444932039713369">"চালু আছে"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"উপলভ্য নেই"</item>
+ <item msgid="9061144428113385092">"বন্ধ আছে"</item>
+ <item msgid="2984256114867200368">"চালু আছে"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"উপলভ্য নেই"</item>
<item msgid="8707481475312432575">"বন্ধ আছে"</item>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index db102a0..7abe5ff 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Uključi"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Uključi"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Ne, hvala"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standardno"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Ekstremno"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Automatsko rotiranje ekrana"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Dozvoliti aplikaciji <xliff:g id="APPLICATION">%1$s</xliff:g> pristup uređaju: <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Dozvoliti aplikaciji <xliff:g id="APPLICATION">%1$s</xliff:g> pristup uređaju <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nOvoj aplikaciji nije dato odobrenje za snimanje, ali može snimati zvuk putem ovog USB uređaja."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatski uključi ponovo sutra"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funkcije kao što su Quick Share, Pronađi moj uređaj i lokacija uređaja koriste Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Dodajte još vidžeta"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Pritisnite i zadržite da prilagodite vidžete"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Prilagodite vidžete"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikacije za onemogućeni vidžet"</string>
<string name="edit_widget" msgid="9030848101135393954">"Uredite vidžet"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Uklanjanje"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodajte vidžet"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Dodirnite da postavite vibraciju."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Dodirnite da isključite zvuk."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Upravljanje bukom"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Dodirnite da promijenite način rada zvuka zvona"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"isključite zvuk"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"uključite zvuk"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Meni napajanja"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Stranica <xliff:g id="ID_1">%1$d</xliff:g> od <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Zaključani ekran"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Možete pronaći telefon putem usluge Pronađi moj uređaj čak i kada je isključen"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Isključivanje…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Pogledajte korake za zaštitu"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Pogledajte korake za zaštitu"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Iskopčajte uređaj"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Za višu rezoluciju obrnite telefon"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Sklopivi uređaj se rasklapa"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Sklopivi uređaj se obrće"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"zatvoreno"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"otvoreno"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Preostalo baterije: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Priključite pisaljku na punjač"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Baterija pisaljke je slaba"</string>
diff --git a/packages/SystemUI/res/values-bs/tiles_states_strings.xml b/packages/SystemUI/res/values-bs/tiles_states_strings.xml
index 217d999..75fb325 100644
--- a/packages/SystemUI/res/values-bs/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-bs/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Isključeno"</item>
<item msgid="578444932039713369">"Uključeno"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Nedostupno"</item>
+ <item msgid="9061144428113385092">"Isključeno"</item>
+ <item msgid="2984256114867200368">"Uključeno"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Nedostupno"</item>
<item msgid="8707481475312432575">"Isključeno"</item>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index c528734..29ef2f9 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Activa"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Activa"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"No, gràcies"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Estàndard"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extrem"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Gira la pantalla automàticament"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Vols permetre que <xliff:g id="APPLICATION">%1$s</xliff:g> accedeixi a <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Vols permetre que <xliff:g id="APPLICATION">%1$s</xliff:g> accedeixi a <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nAquesta aplicació no té permís de gravació, però pot capturar àudio a través d\'aquest dispositiu USB."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Torna\'l a activar automàticament demà"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funcions com ara Quick Share, Troba el meu dispositiu i la ubicació del dispositiu utilitzen el Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Afegeix més widgets"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantén premut per personalitzar els widgets"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalitza els widgets"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icona de l\'aplicació per a widget desactivat"</string>
<string name="edit_widget" msgid="9030848101135393954">"Edita el widget"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Suprimeix"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Afegeix un widget"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Toca per activar la vibració."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Toca per silenciar."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Control de soroll"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Toca per canviar el mode de timbre"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"silenciar"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"deixar de silenciar"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menú d\'engegada"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Pàgina <xliff:g id="ID_1">%1$d</xliff:g> (<xliff:g id="ID_2">%2$d</xliff:g> en total)"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Pantalla de bloqueig"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Pots localitzar aquest telèfon amb Troba el meu dispositiu fins i tot quan estigui apagat"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"S\'està apagant…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Mostra els passos de manteniment"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Mostra els passos de manteniment"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Desconnecta el dispositiu"</string>
@@ -1226,6 +1236,12 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Per a una resolució més alta, gira el telèfon"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositiu plegable desplegant-se"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositiu plegable girant"</string>
+ <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+ <skip />
+ <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+ <skip />
+ <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+ <skip />
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Queda un <xliff:g id="PERCENTAGE">%s</xliff:g> de bateria"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connecta el llapis òptic a un carregador"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Bateria del llapis òptic baixa"</string>
diff --git a/packages/SystemUI/res/values-ca/tiles_states_strings.xml b/packages/SystemUI/res/values-ca/tiles_states_strings.xml
index c1ac5a3..c926e9e 100644
--- a/packages/SystemUI/res/values-ca/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-ca/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Desactivat"</item>
<item msgid="578444932039713369">"Activat"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"No disponible"</item>
+ <item msgid="9061144428113385092">"Desactivat"</item>
+ <item msgid="2984256114867200368">"Activat"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"No disponible"</item>
<item msgid="8707481475312432575">"Desactivat"</item>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index f29dabb..0bbeb9c 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Zapnout"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Zapnout"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Ne, díky"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standardní"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extrémní"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Automatické otočení obrazovky"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Povolit aplikaci <xliff:g id="APPLICATION">%1$s</xliff:g> přístup k zařízení <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Povolit aplikaci <xliff:g id="APPLICATION">%1$s</xliff:g> přístup k zařízení <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nTato aplikace nemá oprávnění k nahrávání, ale může zaznamenávat zvuk prostřednictvím tohoto zařízení USB."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Zítra znovu automaticky zapnout"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funkce jako Quick Share, Najdi moje zařízení a vyhledávání zařízení používají Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Přidat další widgety"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Dlouhým stisknutím můžete přizpůsobit widgety"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Přizpůsobit widgety"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikace s deaktivovaným widgetem"</string>
<string name="edit_widget" msgid="9030848101135393954">"Upravit widget"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Odstranit"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Přidat widget"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Klepnutím nastavíte vibrace."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Klepnutím vypnete zvuk."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Omezení hluku"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Klepnutím změníte režim vyzvánění"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"vypnout zvuk"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"zapnout zvuk"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Nabídka vypínače"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Stránka <xliff:g id="ID_1">%1$d</xliff:g> z <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Obrazovka uzamčení"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Tento telefon můžete pomocí funkce Najdi moje zařízení najít, i když je vypnutý"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Vypínání…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Zobrazit pokyny, co dělat"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Zobrazit pokyny, co dělat"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Odpojte zařízení"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Otočte telefon, abyste dosáhli vyššího rozlišení"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Rozkládání rozkládacího zařízení"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Otáčení rozkládacího zařízení"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"složené"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"rozložené"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Zbývá <xliff:g id="PERCENTAGE">%s</xliff:g> baterie"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Připojte dotykové pero k nabíječce"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Slabá baterie dotykového pera"</string>
diff --git a/packages/SystemUI/res/values-cs/tiles_states_strings.xml b/packages/SystemUI/res/values-cs/tiles_states_strings.xml
index 0a4d4d0..5345569 100644
--- a/packages/SystemUI/res/values-cs/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-cs/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Vypnuto"</item>
<item msgid="578444932039713369">"Zapnuto"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Není k dispozici"</item>
+ <item msgid="9061144428113385092">"Vypnuto"</item>
+ <item msgid="2984256114867200368">"Zapnuto"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Nedostupné"</item>
<item msgid="8707481475312432575">"Vypnuto"</item>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index ec899f2..2ea1e12 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Aktivér"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Aktivér"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Nej tak"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Ekstrem"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Roter skærm automatisk"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Vil du give <xliff:g id="APPLICATION">%1$s</xliff:g> adgang til <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Vil du give <xliff:g id="APPLICATION">%1$s</xliff:g> adgang til <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nDenne app har ikke fået tilladelse til at optage, men optager muligvis lyd via denne USB-enhed."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Aktivér automatisk igen i morgen"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funktioner som f.eks. Quick Share, Find min enhed og enhedslokation anvender Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Tilføj flere widgets"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Hold fingeren nede for at tilpasse widgets"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Tilpas widgets"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Appikon for deaktiveret widget"</string>
<string name="edit_widget" msgid="9030848101135393954">"Rediger widget"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Fjern"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Tilføj widget"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Tryk for at aktivere vibration."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Tryk for at slå lyden fra."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Støjstyring"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Tryk for at ændre ringetilstand"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"slå lyden fra"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"slå lyden til"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menu for afbryderknappen"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Side <xliff:g id="ID_1">%1$d</xliff:g> af <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Låseskærm"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Du kan finde denne telefon med Find min enhed, også selvom den er slukket"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Lukker ned…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Se håndteringsvejledning"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Se håndteringsvejledning"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Træk stikket ud af din enhed"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Vend telefonen for at få højere opløsning"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldbar enhed foldes ud"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldbar enhed vendes om"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"foldet"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"foldet ud"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> batteri tilbage"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Slut din styluspen til en oplader"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Lavt batteriniveau på styluspen"</string>
diff --git a/packages/SystemUI/res/values-da/tiles_states_strings.xml b/packages/SystemUI/res/values-da/tiles_states_strings.xml
index 2391753..5a53149 100644
--- a/packages/SystemUI/res/values-da/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-da/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Fra"</item>
<item msgid="578444932039713369">"Til"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Ikke tilgængelig"</item>
+ <item msgid="9061144428113385092">"Fra"</item>
+ <item msgid="2984256114867200368">"Til"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Ikke tilgængelig"</item>
<item msgid="8707481475312432575">"Fra"</item>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index f7e74c9..edd718f 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Aktivieren"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Aktivieren"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Nein danke"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extrem"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Bildschirm automatisch drehen"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> den Zugriff auf <xliff:g id="USB_DEVICE">%2$s</xliff:g> gewähren?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g> den Zugriff auf <xliff:g id="USB_DEVICE">%2$s</xliff:g> gewähren?\nDiese App hat noch nicht die Berechtigung zum Aufnehmen erhalten, könnte jedoch Audio über dieses USB-Gerät aufnehmen."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Morgen automatisch wieder aktivieren"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Für Funktionen wie Quick Share, „Mein Gerät finden“ und den Gerätestandort wird Bluetooth verwendet"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Weitere Widgets hinzufügen"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Lange drücken, um Widgets anzupassen"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Widgets anpassen"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App-Symbol für deaktiviertes Widget"</string>
<string name="edit_widget" msgid="9030848101135393954">"Widget bearbeiten"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Entfernen"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Widget hinzufügen"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Zum Aktivieren der Vibration tippen."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Zum Stummschalten tippen."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Geräuschunterdrückung"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Zum Ändern des Klingeltonmodus tippen"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"Stummschalten"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"Aufheben der Stummschaltung"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Ein-/Aus-Menü"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Seite <xliff:g id="ID_1">%1$d</xliff:g> von <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Sperrbildschirm"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Du kannst dieses Smartphone über „Mein Gerät finden“ orten, auch wenn es ausgeschaltet ist"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Wird heruntergefahren…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Schritte zur Abkühlung des Geräts ansehen"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Schritte zur Abkühlung des Geräts ansehen"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Gerät vom Stromnetz trennen"</string>
@@ -1226,6 +1236,12 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Für höhere Auflösung Smartphone umdrehen"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Faltbares Gerät wird geöffnet"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Faltbares Gerät wird umgeklappt"</string>
+ <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+ <skip />
+ <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+ <skip />
+ <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+ <skip />
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Akku bei <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Schließe deinen Eingabestift an ein Ladegerät an"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Stylus-Akkustand niedrig"</string>
diff --git a/packages/SystemUI/res/values-de/tiles_states_strings.xml b/packages/SystemUI/res/values-de/tiles_states_strings.xml
index 3aae04b..e5f8655 100644
--- a/packages/SystemUI/res/values-de/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-de/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Aus"</item>
<item msgid="578444932039713369">"An"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Nicht verfügbar"</item>
+ <item msgid="9061144428113385092">"Aus"</item>
+ <item msgid="2984256114867200368">"An"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Nicht verfügbar"</item>
<item msgid="8707481475312432575">"Aus"</item>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 6b341fd..54303b5 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Ενεργοποίηση"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Ενεργοποίηση"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Όχι, ευχαριστώ"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Βασική"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Μέγιστη"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Αυτόματη περιστροφή οθόνης"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Να επιτρέπεται η πρόσβαση της εφαρμογής <xliff:g id="APPLICATION">%1$s</xliff:g> στη συσκευή <xliff:g id="USB_DEVICE">%2$s</xliff:g>;"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Να επιτρέπεται στο <xliff:g id="APPLICATION">%1$s</xliff:g> να έχει πρόσβαση στη συσκευή <xliff:g id="USB_DEVICE">%2$s</xliff:g>;\nΔεν έχει εκχωρηθεί άδεια εγγραφής σε αυτή την εφαρμογή, αλλά μέσω αυτής της συσκευής USB θα μπορεί να εγγράφει ήχο."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Αυτόματη ενεργοποίηση ξανά αύριο"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Λειτουργίες όπως το Quick Share, η Εύρεση συσκευής και η τοποθεσία της συσκευής χρησιμοποιούν Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Προσθήκη περισσότερων γραφικών στοιχείων"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Παρατεταμένο πάτημα για προσαρμογή γραφ. στοιχείων"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Προσαρμογή γραφικών στοιχείων"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Εικονίδιο εφαρμογής για απενεργοποιημένο γραφικό στοιχείο"</string>
<string name="edit_widget" msgid="9030848101135393954">"Επεξεργασία γραφικού στοιχείου"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Κατάργηση"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Προσθήκη γραφικού στοιχείου"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Πατήστε για να ενεργοποιήσετε τη δόνηση."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Πατήστε για σίγαση."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Έλεγχος θορύβου"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Πατήστε για να αλλάξετε τη λειτουργία ειδοποίησης ήχου"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"σίγαση"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"κατάργηση σίγασης"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Μενού λειτουργίας"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Σελίδα <xliff:g id="ID_1">%1$d</xliff:g> από <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Οθόνη κλειδώματος"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Μπορείτε να εντοπίσετε το συγκεκριμένο τηλέφωνο με την Εύρεση συσκευής ακόμα και όταν είναι απενεργοποιημένο"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Τερματισμός λειτουργίας…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Δείτε βήματα αντιμετώπισης."</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Δείτε βήματα αντιμετώπισης."</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Αποσυνδέστε τη συσκευή"</string>
@@ -1226,6 +1236,12 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Για υψηλότερη ανάλυση, αναστρέψτε το τηλέφωνο"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Αναδιπλούμενη συσκευή που ξεδιπλώνει"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Αναδιπλούμενη συσκευή που διπλώνει"</string>
+ <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+ <skip />
+ <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+ <skip />
+ <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+ <skip />
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Απομένει το <xliff:g id="PERCENTAGE">%s</xliff:g> της μπαταρίας"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Συνδέστε τη γραφίδα σε έναν φορτιστή"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Χαμηλή στάθμη μπαταρίας γραφίδας"</string>
diff --git a/packages/SystemUI/res/values-el/tiles_states_strings.xml b/packages/SystemUI/res/values-el/tiles_states_strings.xml
index 035f117..a697711 100644
--- a/packages/SystemUI/res/values-el/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-el/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Ανενεργό"</item>
<item msgid="578444932039713369">"Ενεργό"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Μη διαθέσιμη"</item>
+ <item msgid="9061144428113385092">"Ανενεργή"</item>
+ <item msgid="2984256114867200368">"Ενεργή"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Μη διαθέσιμο"</item>
<item msgid="8707481475312432575">"Ανενεργό"</item>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 021f7db..0fcd2b0 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Turn on"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Turn on"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"No, thanks"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extreme"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Auto-rotate screen"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Allow <xliff:g id="APPLICATION">%1$s</xliff:g> to access <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Allow <xliff:g id="APPLICATION">%1$s</xliff:g> to access <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nThis app has not been granted record permission but could capture audio through this USB device."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatically turn on again tomorrow"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Features like Quick Share, Find My Device and device location use Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -582,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Tap to set to vibrate."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Tap to mute."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Noise control"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Tap to change ringer mode"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"mute"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"unmute"</string>
@@ -1223,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"For higher resolution, flip the phone"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldable device being unfolded"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"folded"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"unfolded"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> battery remaining"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connect your stylus to a charger"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Stylus battery low"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/tiles_states_strings.xml b/packages/SystemUI/res/values-en-rAU/tiles_states_strings.xml
index 2576b60..d97c4c9 100644
--- a/packages/SystemUI/res/values-en-rAU/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Off"</item>
<item msgid="578444932039713369">"On"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Unavailable"</item>
+ <item msgid="9061144428113385092">"Off"</item>
+ <item msgid="2984256114867200368">"On"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Unavailable"</item>
<item msgid="8707481475312432575">"Off"</item>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 14cb7a0..da1dcd8 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Turn on"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Turn on"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"No thanks"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extreme"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Auto-rotate screen"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Allow <xliff:g id="APPLICATION">%1$s</xliff:g> to access <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Allow <xliff:g id="APPLICATION">%1$s</xliff:g> to access <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nThis app has not been granted record permission but could capture audio through this USB device."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatically turn on again tomorrow"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Features like Quick Share, Find My Device, and device location use Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -582,6 +587,10 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Tap to set to vibrate."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Tap to mute."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Noise Control"</string>
+ <string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"Spatial Audio"</string>
+ <string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"Off"</string>
+ <string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"Fixed"</string>
+ <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Head Tracking"</string>
<string name="volume_ringer_change" msgid="3574969197796055532">"Tap to change ringer mode"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"mute"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"unmute"</string>
@@ -1223,6 +1232,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"For higher resolution, flip the phone"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldable device being unfolded"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"folded"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"unfolded"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> battery remaining"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connect your stylus to a charger"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Stylus battery low"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/tiles_states_strings.xml b/packages/SystemUI/res/values-en-rCA/tiles_states_strings.xml
index 2576b60..d97c4c9 100644
--- a/packages/SystemUI/res/values-en-rCA/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Off"</item>
<item msgid="578444932039713369">"On"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Unavailable"</item>
+ <item msgid="9061144428113385092">"Off"</item>
+ <item msgid="2984256114867200368">"On"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Unavailable"</item>
<item msgid="8707481475312432575">"Off"</item>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 021f7db..0fcd2b0 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Turn on"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Turn on"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"No, thanks"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extreme"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Auto-rotate screen"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Allow <xliff:g id="APPLICATION">%1$s</xliff:g> to access <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Allow <xliff:g id="APPLICATION">%1$s</xliff:g> to access <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nThis app has not been granted record permission but could capture audio through this USB device."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatically turn on again tomorrow"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Features like Quick Share, Find My Device and device location use Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -582,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Tap to set to vibrate."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Tap to mute."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Noise control"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Tap to change ringer mode"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"mute"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"unmute"</string>
@@ -1223,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"For higher resolution, flip the phone"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldable device being unfolded"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"folded"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"unfolded"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> battery remaining"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connect your stylus to a charger"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Stylus battery low"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/tiles_states_strings.xml b/packages/SystemUI/res/values-en-rGB/tiles_states_strings.xml
index 2576b60..d97c4c9 100644
--- a/packages/SystemUI/res/values-en-rGB/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Off"</item>
<item msgid="578444932039713369">"On"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Unavailable"</item>
+ <item msgid="9061144428113385092">"Off"</item>
+ <item msgid="2984256114867200368">"On"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Unavailable"</item>
<item msgid="8707481475312432575">"Off"</item>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 021f7db..0fcd2b0 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Turn on"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Turn on"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"No, thanks"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extreme"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Auto-rotate screen"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Allow <xliff:g id="APPLICATION">%1$s</xliff:g> to access <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Allow <xliff:g id="APPLICATION">%1$s</xliff:g> to access <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nThis app has not been granted record permission but could capture audio through this USB device."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatically turn on again tomorrow"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Features like Quick Share, Find My Device and device location use Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -582,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Tap to set to vibrate."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Tap to mute."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Noise control"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Tap to change ringer mode"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"mute"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"unmute"</string>
@@ -1223,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"For higher resolution, flip the phone"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldable device being unfolded"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"folded"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"unfolded"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> battery remaining"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connect your stylus to a charger"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Stylus battery low"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/tiles_states_strings.xml b/packages/SystemUI/res/values-en-rIN/tiles_states_strings.xml
index 2576b60..d97c4c9 100644
--- a/packages/SystemUI/res/values-en-rIN/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Off"</item>
<item msgid="578444932039713369">"On"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Unavailable"</item>
+ <item msgid="9061144428113385092">"Off"</item>
+ <item msgid="2984256114867200368">"On"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Unavailable"</item>
<item msgid="8707481475312432575">"Off"</item>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index af690e4..516c3af 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Turn on"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Turn on"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"No thanks"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extreme"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Auto-rotate screen"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Allow <xliff:g id="APPLICATION">%1$s</xliff:g> to access <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Allow <xliff:g id="APPLICATION">%1$s</xliff:g> to access <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nThis app has not been granted record permission but could capture audio through this USB device."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatically turn on again tomorrow"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Features like Quick Share, Find My Device, and device location use Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -582,6 +587,10 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Tap to set to vibrate."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Tap to mute."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Noise Control"</string>
+ <string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"Spatial Audio"</string>
+ <string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"Off"</string>
+ <string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"Fixed"</string>
+ <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Head Tracking"</string>
<string name="volume_ringer_change" msgid="3574969197796055532">"Tap to change ringer mode"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"mute"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"unmute"</string>
@@ -1223,6 +1232,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"For higher resolution, flip the phone"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Foldable device being unfolded"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Foldable device being flipped around"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"folded"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"unfolded"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> battery remaining"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connect your stylus to a charger"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Stylus battery low"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/tiles_states_strings.xml b/packages/SystemUI/res/values-en-rXC/tiles_states_strings.xml
index 42daf8a..2facf58 100644
--- a/packages/SystemUI/res/values-en-rXC/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Off"</item>
<item msgid="578444932039713369">"On"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Unavailable"</item>
+ <item msgid="9061144428113385092">"Off"</item>
+ <item msgid="2984256114867200368">"On"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Unavailable"</item>
<item msgid="8707481475312432575">"Off"</item>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 1798e9a..11e31e1 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Activar"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Activar"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"No, gracias"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Estándar"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extremo"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Girar la pantalla automáticamente"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"¿Deseas permitir que <xliff:g id="APPLICATION">%1$s</xliff:g> acceda a <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"¿Quieres permitir que <xliff:g id="APPLICATION">%1$s</xliff:g> acceda a <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nLa app no tiene permiso para grabar, pero podría capturar audio mediante este dispositivo USB."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Volver a activar automáticamente mañana"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Las funciones como Quick Share, Encontrar mi dispositivo y la ubicación del dispositivo usan Bluetooth."</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -582,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Presiona para establecer el modo vibración."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Presiona para silenciar."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Control de ruido"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Presiona para cambiar el modo de timbre"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"silenciar"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"dejar de silenciar"</string>
@@ -796,8 +809,8 @@
<string name="right_keycode" msgid="2480715509844798438">"Clave de código derecho"</string>
<string name="left_icon" msgid="5036278531966897006">"Ícono izquierdo"</string>
<string name="right_icon" msgid="1103955040645237425">"Ícono derecho"</string>
- <string name="drag_to_add_tiles" msgid="8933270127508303672">"Mantén presionado y arrastra para agregar tarjetas"</string>
- <string name="drag_to_rearrange_tiles" msgid="2143204300089638620">"Mantén presionado y arrastra para reorganizar las tarjetas"</string>
+ <string name="drag_to_add_tiles" msgid="8933270127508303672">"Mantén presionada la tarjeta y arrástrala para agregarla"</string>
+ <string name="drag_to_rearrange_tiles" msgid="2143204300089638620">"Mantén presionada la tarjeta y arrástrala para reorganizarla"</string>
<string name="drag_to_remove_tiles" msgid="4682194717573850385">"Arrastra aquí para quitar"</string>
<string name="drag_to_remove_disabled" msgid="933046987838658850">"Necesitas al menos <xliff:g id="MIN_NUM_TILES">%1$d</xliff:g> tarjetas"</string>
<string name="qs_edit" msgid="5583565172803472437">"Editar"</string>
@@ -1223,6 +1236,12 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Para obtener una resolución más alta, gira el teléfono"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo plegable siendo desplegado"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo plegable siendo girado"</string>
+ <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+ <skip />
+ <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+ <skip />
+ <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+ <skip />
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> de batería restante"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Conecta tu pluma stylus a un cargador"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"La pluma stylus tiene poca batería"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/tiles_states_strings.xml b/packages/SystemUI/res/values-es-rUS/tiles_states_strings.xml
index 09abc54..6446bdf 100644
--- a/packages/SystemUI/res/values-es-rUS/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Desactivado"</item>
<item msgid="578444932039713369">"Activado"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"No disponible"</item>
+ <item msgid="9061144428113385092">"Desactivado"</item>
+ <item msgid="2984256114867200368">"Activado"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"No disponible"</item>
<item msgid="8707481475312432575">"Desactivado"</item>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 73c913f..ec03f8d 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Activar"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Activar"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"No, gracias"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Estándar"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extremo"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Girar pantalla automáticamente"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"¿Permitir que <xliff:g id="APPLICATION">%1$s</xliff:g> acceda a <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"¿Quieres que <xliff:g id="APPLICATION">%1$s</xliff:g> pueda acceder a <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nEsta aplicación no tiene permisos para grabar, pero podría capturar audio a través de este dispositivo USB."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Volver a activar automáticamente mañana"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funciones como Quick Share, Encontrar mi dispositivo y la ubicación del dispositivo usan Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Añade más widgets"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantén pulsado para personalizar los widgets"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icono de la aplicación de widget inhabilitado"</string>
<string name="edit_widget" msgid="9030848101135393954">"Editar widget"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Quitar"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Añadir widget"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Toca para activar la vibración."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Toca para silenciar."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Control de ruido"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Toca para cambiar el modo de timbre"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"silenciar"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"dejar de silenciar"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menú de encendido"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Página <xliff:g id="ID_1">%1$d</xliff:g> de <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Pantalla de bloqueo"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Puedes localizar este teléfono con Encontrar mi dispositivo, aunque esté apagado"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Apagando…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Ver pasos de mantenimiento"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ver pasos de mantenimiento"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Desenchufa tu dispositivo"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Para una mayor resolución, gira el teléfono"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo plegable desplegándose"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo plegable mostrado desde varios ángulos"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"plegado"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"desplegado"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Batería restante: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Conecta tu lápiz óptico a un cargador"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Batería del lápiz óptico baja"</string>
diff --git a/packages/SystemUI/res/values-es/tiles_states_strings.xml b/packages/SystemUI/res/values-es/tiles_states_strings.xml
index 83b4627..4cc0c67 100644
--- a/packages/SystemUI/res/values-es/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-es/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Desactivado"</item>
<item msgid="578444932039713369">"Activado"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"No disponible"</item>
+ <item msgid="9061144428113385092">"Desactivado"</item>
+ <item msgid="2984256114867200368">"Activado"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"No disponible"</item>
<item msgid="8707481475312432575">"Desactivado"</item>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 6fa7044..dfbb1ee 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Lülita sisse"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Lülita sisse"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Tänan, ei"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Tavaline"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Ekstreemne"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Kuva automaatne pööramine"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Kas lubada rakendusele <xliff:g id="APPLICATION">%1$s</xliff:g> juurdepääs seadmele <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Kas lubada rakendusel <xliff:g id="APPLICATION">%1$s</xliff:g> seadmele <xliff:g id="USB_DEVICE">%2$s</xliff:g> juurde pääseda?\nSellele rakendusele pole antud salvestamise luba, kuid see saab heli jäädvustada selle USB-seadme kaudu."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Lülita automaatselt homme uuesti sisse"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funktsioonid, nagu Kiirjagamine, Leia mu seade ja seadme asukoht, kasutavad Bluetoothi"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Lisage rohkem vidinaid"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Vajutage pikalt vidinate kohandamiseks"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Kohanda vidinaid"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Keelatud vidina rakenduseikoon"</string>
<string name="edit_widget" msgid="9030848101135393954">"Muuda vidinat"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Eemalda"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Lisa vidin"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Puudutage vibreerimise määramiseks."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Puudutage vaigistamiseks."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Mürasummutus"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Puudutage telefonihelina režiimi muutmiseks"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"vaigistamine"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"vaigistuse tühistamine"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Toitemenüü"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Leht <xliff:g id="ID_1">%1$d</xliff:g>/<xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Lukustuskuva"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Saate selle telefoni funktsiooniga Leia mu seade leida ka siis, kui see on välja lülitatud"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Väljalülitamine …"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Vaadake hooldusjuhiseid"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Vaadake hooldusjuhiseid"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Eemaldage seade"</string>
@@ -1226,6 +1236,12 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Suurema eraldusvõime saavutamiseks pöörake telefon ümber"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Volditava seadme lahtivoltimine"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Volditava seadme ümberpööramine"</string>
+ <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+ <skip />
+ <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+ <skip />
+ <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+ <skip />
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Akutase on <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Ühendage elektronpliiats laadijaga"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Elektronpliiatsi akutase on madal"</string>
diff --git a/packages/SystemUI/res/values-et/tiles_states_strings.xml b/packages/SystemUI/res/values-et/tiles_states_strings.xml
index 4f0551d..f16d552 100644
--- a/packages/SystemUI/res/values-et/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-et/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Väljas"</item>
<item msgid="578444932039713369">"Sees"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Pole saadaval"</item>
+ <item msgid="9061144428113385092">"Väljas"</item>
+ <item msgid="2984256114867200368">"Sees"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Pole saadaval"</item>
<item msgid="8707481475312432575">"Väljas"</item>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 564fbb3..e41333c 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Aktibatu"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Aktibatu"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Ez, eskerrik asko"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Arrunta"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Muturrekoa"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Biratu pantaila automatikoki"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> erabiltzeko baimena eman nahi diozu <xliff:g id="APPLICATION">%1$s</xliff:g> aplikazioari?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> erabiltzeko baimena eman nahi diozu <xliff:g id="APPLICATION">%1$s</xliff:g> aplikazioari?\nAplikazioak ez du grabatzeko baimenik, baina baliteke USB bidezko gailu horren bidez audioa grabatzea."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Aktibatu automatikoki berriro bihar"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Quick Share, Bilatu nire gailua, gailuaren kokapena eta beste eginbide batzuek Bluetootha darabilte"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Gehitu widget gehiago"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Widgetak pertsonalizatzeko, sakatu luze"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Pertsonalizatu widgetak"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Desgaitutako widgetaren aplikazio-ikonoa"</string>
<string name="edit_widget" msgid="9030848101135393954">"Editatu widgeta"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Kendu"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Gehitu widget bat"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Sakatu hau dardara ezartzeko."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Sakatu hau audioa desaktibatzeko."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Zarata-murrizketa"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Sakatu tonu-jotzailearen modua aldatzeko"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"desaktibatu audioa"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"aktibatu audioa"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Itzaltzeko menua"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_1">%1$d</xliff:g>/<xliff:g id="ID_2">%2$d</xliff:g> orria"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Pantaila blokeatua"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Itzalita badago ere aurki dezakezu telefonoa Bilatu nire gailua erabilita"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Itzaltzen…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Ikusi zaintzeko urratsak"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ikusi zaintzeko urratsak"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Deskonektatu gailua"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Irauli telefonoa bereizmen handiago a lortzeko"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Gailu tolesgarria zabaltzen"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Gailu tolesgarria biratzen"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"tolestuta"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"tolestu gabe"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Bateriaren <xliff:g id="PERCENTAGE">%s</xliff:g> geratzen da"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Konektatu arkatza kargagailu batera"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Arkatzak bateria gutxi du"</string>
diff --git a/packages/SystemUI/res/values-eu/tiles_states_strings.xml b/packages/SystemUI/res/values-eu/tiles_states_strings.xml
index accecac..0f6570c 100644
--- a/packages/SystemUI/res/values-eu/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-eu/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Desaktibatuta"</item>
<item msgid="578444932039713369">"Aktibatuta"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Ez dago erabilgarri"</item>
+ <item msgid="9061144428113385092">"Desaktibatuta"</item>
+ <item msgid="2984256114867200368">"Aktibatuta"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Ez dago erabilgarri"</item>
<item msgid="8707481475312432575">"Desaktibatuta"</item>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 95f17b0..6b5c4cc 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"روشن کردن"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"روشن کردن"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"نه متشکرم"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"استاندارد"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"نهایت"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"چرخش خودکار صفحه"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"به <xliff:g id="APPLICATION">%1$s</xliff:g> برای دسترسی به <xliff:g id="USB_DEVICE">%2$s</xliff:g> اجازه داده شود؟"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"به <xliff:g id="APPLICATION">%1$s</xliff:g> اجازه میدهید به <xliff:g id="USB_DEVICE">%2$s</xliff:g>دسترسی داشته باشد؟\nمجوز ضبط به این برنامه داده نشده است اما میتواند صدا را ازطریق این دستگاه USB ضبط کند."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"فردا دوباره بهطور خودکار روشن شود"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"ویژگیهایی مثل «همرسانی سریع»، «پیدا کردن دستگاهم»، و مکان دستگاه از بلوتوث استفاده میکنند"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"افزودن ابزارکهای بیشتر"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"برای سفارشیسازی ابزارکها، فشار طولانی دهید"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"سفارشیسازی ابزارکها"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"نماد برنامه برای ابزارک غیرفعال"</string>
<string name="edit_widget" msgid="9030848101135393954">"ویرایش ابزارک"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"برداشتن"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"افزودن ابزارک"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. برای تنظیم روی لرزش، ضربه بزنید."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. برای صامت کردن ضربه بزنید."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"کنترل صدای محیط"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"برای تغییر حالت زنگ، ضربه بزنید"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"صامت کردن"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"باصدا کردن"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"منوی روشن/خاموش"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"صفحه <xliff:g id="ID_1">%1$d</xliff:g> از <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"صفحه قفل"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"حتی وقتی این تلفن خاموش است، میتوانید با «پیدا کردن دستگاهم» آن را مکانیابی کنید"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"درحال خاموش شدن…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"دیدن اقدامات محافظتی"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"دیدن اقدامات محافظتی"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"دستگاه را جدا کنید"</string>
@@ -1223,9 +1233,15 @@
<string name="rear_display_folded_bottom_sheet_title" msgid="3930008746560711990">"باز کردن تلفن"</string>
<string name="rear_display_unfolded_bottom_sheet_title" msgid="6291111173057304055">"صفحهها جابهجا شود؟"</string>
<string name="rear_display_folded_bottom_sheet_description" msgid="6842767125783222695">"برای وضوح بیشتر، از دوربین پشت استفاده کنید"</string>
- <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"برای وضوح بیشتر، تلفن را برگردانید"</string>
+ <string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"برای وضوح بیشتر، تلفن را بچرخانید"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"دستگاه تاشو درحال باز شدن"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"دستگاه تاشو درحال چرخش به اطراف"</string>
+ <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+ <skip />
+ <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+ <skip />
+ <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+ <skip />
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> باتری باقی مانده است"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"قلم را به شارژر وصل کنید"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"باتری قلم ضعیف است"</string>
diff --git a/packages/SystemUI/res/values-fa/tiles_states_strings.xml b/packages/SystemUI/res/values-fa/tiles_states_strings.xml
index 01a549e..6d6954e 100644
--- a/packages/SystemUI/res/values-fa/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-fa/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"خاموش"</item>
<item msgid="578444932039713369">"روشن"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"دردسترس نیست"</item>
+ <item msgid="9061144428113385092">"خاموش"</item>
+ <item msgid="2984256114867200368">"روشن"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"دردسترس نیست"</item>
<item msgid="8707481475312432575">"خاموش"</item>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index ab022dd..78f401f 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Laita päälle"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Laita päälle"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Ei kiitos"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Tavallinen"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extreme"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Näytön automaattinen kääntö"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Saako <xliff:g id="APPLICATION">%1$s</xliff:g> käyttöoikeuden (<xliff:g id="USB_DEVICE">%2$s</xliff:g>)?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Saako <xliff:g id="APPLICATION">%1$s</xliff:g> tämän pääsyoikeuden: <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nSovellus ei ole saanut tallennuslupaa, mutta voi tallentaa ääntä tämän USB-laitteen avulla."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Laita automaattisesti päälle taas huomenna"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Ominaisuudet (esim. Quick Share ja Paikanna laite) ja laitteen sijainti käyttävät Bluetoothia"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Lisää widgetejä"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Yksilöi widgetit pitkällä painalluksella"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Muokkaa widgettejä"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Käytöstä poistetun widgetin sovelluskuvake"</string>
<string name="edit_widget" msgid="9030848101135393954">"Muokkaa widgetiä"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Poista"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Lisää widget"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Siirry värinätilaan napauttamalla."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Mykistä napauttamalla."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Melunvaimennus"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Vaihda soittoäänen tilaa napauttamalla"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"mykistä"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"poista mykistys"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Virtavalikko"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Sivu <xliff:g id="ID_1">%1$d</xliff:g>/<xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Lukitusnäyttö"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Voit löytää tämän puhelimen Paikanna laite ‑sovelluksella, vaikka se olisi sammutettuna"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Sammutetaan…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Katso huoltovaiheet"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Katso huoltovaiheet"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Irrota laite"</string>
@@ -1226,6 +1236,12 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Resoluutio on parempi, kun käännät puhelimen"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Taitettava laite taitetaan"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Taitettava laite käännetään ympäri"</string>
+ <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+ <skip />
+ <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+ <skip />
+ <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+ <skip />
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Akkua jäljellä <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Yhdistä näyttökynä laturiin"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Näyttökynän akku vähissä"</string>
diff --git a/packages/SystemUI/res/values-fi/tiles_states_strings.xml b/packages/SystemUI/res/values-fi/tiles_states_strings.xml
index f7a8ec9..545abc9 100644
--- a/packages/SystemUI/res/values-fi/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-fi/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Poissa päältä"</item>
<item msgid="578444932039713369">"Päällä"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Ei saatavilla"</item>
+ <item msgid="9061144428113385092">"Pois päältä"</item>
+ <item msgid="2984256114867200368">"Päällä"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Ei saatavilla"</item>
<item msgid="8707481475312432575">"Poissa päältä"</item>
diff --git a/packages/SystemUI/res/values-fr-feminine/strings.xml b/packages/SystemUI/res/values-fr-feminine/strings.xml
new file mode 100644
index 0000000..ebdc3fb
--- /dev/null
+++ b/packages/SystemUI/res/values-fr-feminine/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (c) 2009, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="zen_priority_introduction" msgid="3159291973383796646">"Vous ne serez pas dérangée par des sons ou des vibrations, hormis ceux des alarmes, des rappels, des événements et des appelants de votre choix. Vous entendrez encore les sons que vous choisirez de jouer, notamment la musique, les vidéos et les jeux."</string>
+ <string name="zen_alarms_introduction" msgid="3987266042682300470">"Vous ne serez pas dérangée par des sons ou des vibrations, hormis ceux des alarmes. Vous entendrez encore les sons que vous choisirez de jouer, notamment la musique, les vidéos et les jeux."</string>
+ <string name="guest_wipe_session_title" msgid="7147965814683990944">"Heureux de vous revoir, Invitée"</string>
+</resources>
diff --git a/packages/SystemUI/res/values-fr-masculine/strings.xml b/packages/SystemUI/res/values-fr-masculine/strings.xml
new file mode 100644
index 0000000..6b94970
--- /dev/null
+++ b/packages/SystemUI/res/values-fr-masculine/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (c) 2009, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="zen_priority_introduction" msgid="3159291973383796646">"Vous ne serez pas dérangé par des sons ou des vibrations, hormis ceux des alarmes, des rappels, des événements et des appelants de votre choix. Vous entendrez encore les sons que vous choisirez de jouer, notamment la musique, les vidéos et les jeux."</string>
+ <string name="zen_alarms_introduction" msgid="3987266042682300470">"Vous ne serez pas dérangé par des sons ou des vibrations, hormis ceux des alarmes. Vous entendrez encore les sons que vous choisirez de jouer, notamment la musique, les vidéos et les jeux."</string>
+ <string name="guest_wipe_session_title" msgid="7147965814683990944">"Heureux de vous revoir, Invité"</string>
+</resources>
diff --git a/packages/SystemUI/res/values-fr-neuter/strings.xml b/packages/SystemUI/res/values-fr-neuter/strings.xml
new file mode 100644
index 0000000..e9d0191
--- /dev/null
+++ b/packages/SystemUI/res/values-fr-neuter/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright (c) 2009, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="zen_priority_introduction" msgid="3159291973383796646">"Vous ne serez pas dérangé·e par des sons ou des vibrations, hormis ceux des alarmes, des rappels, des événements et des appelants de votre choix. Vous entendrez encore les sons que vous choisirez de jouer, notamment la musique, les vidéos et les jeux."</string>
+ <string name="zen_alarms_introduction" msgid="3987266042682300470">"Vous ne serez pas dérangé·e par des sons ou des vibrations, hormis ceux des alarmes. Vous entendrez encore les sons que vous choisirez de jouer, notamment la musique, les vidéos et les jeux."</string>
+ <string name="guest_wipe_session_title" msgid="7147965814683990944">"Heureux de vous revoir, Invité·e"</string>
+</resources>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 1186c81..4c4913e 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Activer"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Activer"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Non merci"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extrême"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Rotation auto de l\'écran"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Autoriser <xliff:g id="APPLICATION">%1$s</xliff:g> à accéder à <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Autorisé <xliff:g id="APPLICATION">%1$s</xliff:g> à accéder à <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nCette application n\'a pas été autorisée à effectuer des enregistrements, mais elle pourrait enregistrer du contenu audio par l\'intermédiaire de cet appareil USB."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Activer le Bluetooth automatiquement demain"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Les fonctionnalités comme le Partage rapide, Localiser mon appareil et la position de l\'appareil utilisent le Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Ajouter plus de widgets"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Maintenez le doigt pour personnaliser les widgets"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personnaliser les widgets"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icône d\'application pour un widget désactivé"</string>
<string name="edit_widget" msgid="9030848101135393954">"Modifier le widget"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Retirer"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ajouter un widget"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Touchez pour activer les vibrations."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Touchez pour couper le son."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Contrôle du bruit"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Touchez pour modifier le mode de sonnerie"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"désactiver le son"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"réactiver le son"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menu de l\'interrupteur"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Page <xliff:g id="ID_1">%1$d</xliff:g> sur <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Écran de verrouillage"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Vous pouvez localiser ce téléphone à l\'aide de Localiser mon appareil, même lorsqu\'il est éteint"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Arrêt en cours…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Afficher les étapes d\'entretien"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Afficher les étapes d\'entretien"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Débranchez votre appareil"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Pour une meilleure résolution, retournez le téléphone"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Appareil pliable en cours de dépliage"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Appareil pliable en train d\'être retourné"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"plié"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"déplié"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Charge restante de la pile : <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connectez votre stylet à un chargeur"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Pile du stylet faible"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml b/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml
index 7b9708e..d89484d 100644
--- a/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Désactivé"</item>
<item msgid="578444932039713369">"Activé"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Non accessible"</item>
+ <item msgid="9061144428113385092">"Désactivé"</item>
+ <item msgid="2984256114867200368">"Activé"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Non disponible"</item>
<item msgid="8707481475312432575">"Désactivé"</item>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index a02c9f7..5bd34d7 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Activer"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Activer"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Non, merci"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Ultra"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Rotation automatique de l\'écran"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Autoriser <xliff:g id="APPLICATION">%1$s</xliff:g> à accéder à <xliff:g id="USB_DEVICE">%2$s</xliff:g> ?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Autoriser <xliff:g id="APPLICATION">%1$s</xliff:g> à accéder à <xliff:g id="USB_DEVICE">%2$s</xliff:g> ?\nCette application n\'a pas été autorisée à effectuer des enregistrements, mais elle pourrait enregistrer du contenu audio via ce périphérique USB."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Réactiver automatiquement demain"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Certaines fonctionnalités telles que Quick Share, Localiser mon appareil ou encore la position de l\'appareil utilisent le Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Ajouter des widgets"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Appuyez de manière prolongée pour personnaliser les widgets"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personnaliser les widgets"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icône d\'appli du widget désactivé"</string>
<string name="edit_widget" msgid="9030848101135393954">"Modifier le widget"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Supprimer"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ajouter un widget"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Appuyez pour mettre en mode vibreur."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Appuyez pour ignorer."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Contrôle du bruit"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Appuyez pour changer le mode de la sonnerie"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"couper le son"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"réactiver le son"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menu Marche/Arrêt"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Page <xliff:g id="ID_1">%1$d</xliff:g> sur <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Écran de verrouillage"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Vous pouvez localiser ce téléphone avec Localiser mon appareil même lorsqu\'il est éteint"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Arrêt…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Afficher les étapes d\'entretien"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Afficher les étapes d\'entretien"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Débrancher votre appareil"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Pour une résolution plus élevée, retournez le téléphone"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Appareil pliable qui est déplié"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Appareil pliable qui est retourné"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"plié"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"déplié"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> de batterie restante"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connectez votre stylet à un chargeur"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"La batterie du stylet est faible"</string>
diff --git a/packages/SystemUI/res/values-fr/tiles_states_strings.xml b/packages/SystemUI/res/values-fr/tiles_states_strings.xml
index af1d09d..a560ff0 100644
--- a/packages/SystemUI/res/values-fr/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-fr/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Désactivé"</item>
<item msgid="578444932039713369">"Activé"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Non disponible"</item>
+ <item msgid="9061144428113385092">"Désactivé"</item>
+ <item msgid="2984256114867200368">"Activé"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Indisponible"</item>
<item msgid="8707481475312432575">"Désactivée"</item>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index e758af8..e694d14 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Activar"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Activar"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Non, grazas"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Estándar"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"extremo"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Xirar pantalla automaticamente"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Queres permitir que <xliff:g id="APPLICATION">%1$s</xliff:g> acceda a <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Queres permitir que a aplicación <xliff:g id="APPLICATION">%1$s</xliff:g> acceda ao dispositivo (<xliff:g id="USB_DEVICE">%2$s</xliff:g>)?\nEsta aplicación non está autorizada para realizar gravacións, pero podería capturar audio a través deste dispositivo USB."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Volver activar automaticamente mañá"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"As funcións como Quick Share, Localizar o meu dispositivo ou a de localización do dispositivo utilizan o Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Engadir máis widgets"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Pulsación longa para personalizar os widgets"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icona da aplicación de widget desactivado"</string>
<string name="edit_widget" msgid="9030848101135393954">"Editar widget"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Quitar"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Engadir widget"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Toca para establecer a vibración."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Toca para silenciar."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Control de ruído"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Toca para cambiar o modo de timbre"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"silenciar"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"activar o son"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menú de acendido"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Páxina <xliff:g id="ID_1">%1$d</xliff:g> de <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Pantalla de bloqueo"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Podes atopar este teléfono (mesmo se está apagado) con Localizar o meu dispositivo"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Apagando…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Ver pasos de mantemento"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ver pasos de mantemento"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Desconectar o dispositivo"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Dálle a volta ao teléfono para gozar dunha maior resolución"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo pregable abríndose"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo pregable xirando"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"dispositivo pregado"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"dispositivo despregado"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Batería restante: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Conecta o lapis óptico a un cargador"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"O lapis óptico ten pouca batería"</string>
diff --git a/packages/SystemUI/res/values-gl/tiles_states_strings.xml b/packages/SystemUI/res/values-gl/tiles_states_strings.xml
index a963dec..1cde1ab 100644
--- a/packages/SystemUI/res/values-gl/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-gl/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Non"</item>
<item msgid="578444932039713369">"Si"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Opción non dispoñible"</item>
+ <item msgid="9061144428113385092">"Opción desactivada"</item>
+ <item msgid="2984256114867200368">"Opción activada"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Non dispoñible"</item>
<item msgid="8707481475312432575">"Non"</item>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 6d1d8df..5099773 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"ચાલુ કરો"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"ચાલુ કરો"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"ના, આભાર"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"સ્ટૅન્ડર્ડ"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"એક્સ્ટ્રીમ"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"ઑટો રોટેટ સ્ક્રીન"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="USB_DEVICE">%2$s</xliff:g>ના ઍક્સેસ માટે <xliff:g id="APPLICATION">%1$s</xliff:g>ને મંજૂરી આપીએ?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g>ને <xliff:g id="USB_DEVICE">%2$s</xliff:g> ઍક્સેસ કરવાની મંજૂરી આપીએ?\nઆ ઍપને રેકૉર્ડ કરવાની પરવાનગી આપવામાં આવી નથી પરંતુ તે આ USB ડિવાઇસ મારફત ઑડિયો કૅપ્ચર કરી શકે છે."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"આવતીકાલે ફરીથી ઑટોમૅટિક રીતે ચાલુ કરો"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"ક્વિક શેર, Find My Device અને ડિવાઇસના લોકેશન જેવી સુવિધાઓ બ્લૂટૂથનો ઉપયોગ કરે છે"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"વધુ વિજેટ ઉમેરો"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"વિજેટ કસ્ટમાઇઝ કરવા માટે થોડીવાર દબાવી રાખો"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"વિજેટ કસ્ટમાઇઝ કરો"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"બંધ કરેલા વિજેટ માટેની ઍપનું આઇકન"</string>
<string name="edit_widget" msgid="9030848101135393954">"વિજેટમાં ફેરફાર કરો"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"કાઢી નાખો"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"વિજેટ ઉમેરો"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. કંપન પર સેટ કરવા માટે ટૅપ કરો."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. મ્યૂટ કરવા માટે ટૅપ કરો."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"અવાજનું નિયંત્રણ"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"રિંગર મોડ બદલવા માટે ટૅપ કરો"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"મ્યૂટ કરો"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"અનમ્યૂટ કરો"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"પાવર મેનૂ"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g> માંથી <xliff:g id="ID_1">%1$d</xliff:g> પૃષ્ઠ"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"લૉક સ્ક્રીન"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"આ ફોનનો પાવર બંધ હોય ત્યારે પણ Find My Device વડે તમે તેનું લોકેશન જાણી શકો છો"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"શટ ડાઉન કરી રહ્યાં છીએ…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"સારસંભાળના પગલાં જુઓ"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"સારસંભાળના પગલાં જુઓ"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"તમારા ડિવાઇસને અનપ્લગ કરો"</string>
@@ -1226,6 +1236,12 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"વધુ રિઝોલ્યુશન માટે, ફોનને ફ્લિપ કરો"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ફોલ્ડ કરી શકાય એવું ડિવાઇસ અનફોલ્ડ કરવામાં આવી રહ્યું છે"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ફોલ્ડ કરી શકાય એવું ડિવાઇસ ફ્લિપ કરવામાં આવી રહ્યું છે"</string>
+ <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+ <skip />
+ <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+ <skip />
+ <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+ <skip />
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> બૅટરી બાકી છે"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"તમારા સ્ટાઇલસને ચાર્જર સાથે કનેક્ટ કરો"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"સ્ટાઇલસની બૅટરીમાં ચાર્જ ઓછો છે"</string>
diff --git a/packages/SystemUI/res/values-gu/tiles_states_strings.xml b/packages/SystemUI/res/values-gu/tiles_states_strings.xml
index 580ec10..65b6133 100644
--- a/packages/SystemUI/res/values-gu/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-gu/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"બંધ છે"</item>
<item msgid="578444932039713369">"ચાલુ છે"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"અનુપલબ્ધ છે"</item>
+ <item msgid="9061144428113385092">"બંધ છે"</item>
+ <item msgid="2984256114867200368">"ચાલુ છે"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"ઉપલબ્ધ નથી"</item>
<item msgid="8707481475312432575">"બંધ છે"</item>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 2035429..ea4ccc0 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"चालू करें"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"चालू करें"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"रहने दें"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"स्टैंडर्ड मोड"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"एक्सट्रीम"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"स्क्रीन अपने आप घुमाना"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> को <xliff:g id="USB_DEVICE">%2$s</xliff:g> के ऐक्सेस की अनुमति दें?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"आप <xliff:g id="APPLICATION">%1$s</xliff:g> को <xliff:g id="USB_DEVICE">%2$s</xliff:g> ऐक्सेस करने की अनुमति देना चाहते हैं?\nइस ऐप्लिकेशन को रिकॉर्ड करने की अनुमति नहीं दी गई है. हालांकि, ऐप्लिकेशन इस यूएसबी डिवाइस से ऑडियो कैप्चर कर सकता है."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"कल फिर से अपने-आप चालू हो जाएगा"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"क्विक शेयर, Find My Device, और डिवाइस की जगह की जानकारी जैसी सुविधाएं ब्लूटूथ का इस्तेमाल करती हैं"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"ज़्यादा विजेट जोड़ें"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"विजेट पसंद के मुताबिक बनाने के लिए उसे दबाकर रखें"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"विजेट अपनी पसंद के मुताबिक बनाएं"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"बंद किए गए विजेट के लिए ऐप्लिकेशन आइकॉन"</string>
<string name="edit_widget" msgid="9030848101135393954">"विजेट में बदलाव करें"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"हटाएं"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"विजेट जोड़ें"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. कंपन (वाइब्रेशन) पर सेट करने के लिए छूएं."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. म्यूट करने के लिए टैप करें."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"शोर को कंट्रोल करने की सुविधा"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"रिंगर मोड बदलने के लिए टैप करें"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"म्यूट करें"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"अनम्यूट करें"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"पावर मेन्यू"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"पेज <xliff:g id="ID_2">%2$d</xliff:g> में से <xliff:g id="ID_1">%1$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"लॉक स्क्रीन"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Find My Device की मदद से, फ़ोन बंद होने पर भी इस फ़ोन की जगह की जानकारी का पता लगाया जा सकता है"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"बंद हो रहा है…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"डिवाइस के रखरखाव के तरीके देखें"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"डिवाइस के रखरखाव के तरीके देखें"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"डिवाइस को अनप्लग करें"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"बेहतर रिज़ॉल्यूशन वाली फ़ोटो खींचने के लिए, फ़ोन को फ़्लिप करें"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"फ़ोल्ड किया जा सकने वाला डिवाइस अनफ़ोल्ड किया जा रहा है"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"फ़ोल्ड किया जा सकने वाला डिवाइस पलटा जा रहा है"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"डिवाइस फ़ोल्ड किया गया"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"डिवाइस अनफ़ोल्ड किया गया"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> बैटरी बची है"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"अपने स्टाइलस को चार्ज करें"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"स्टाइलस की बैटरी कम है"</string>
diff --git a/packages/SystemUI/res/values-hi/tiles_states_strings.xml b/packages/SystemUI/res/values-hi/tiles_states_strings.xml
index 3fd0b30..b49d3b9 100644
--- a/packages/SystemUI/res/values-hi/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-hi/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"बंद है"</item>
<item msgid="578444932039713369">"चालू है"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"उपलब्ध नहीं है"</item>
+ <item msgid="9061144428113385092">"बंद है"</item>
+ <item msgid="2984256114867200368">"चालू है"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"उपलब्ध नहीं है"</item>
<item msgid="8707481475312432575">"बंद है"</item>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index d50a951..a6cfeb9 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Uključi"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Uključi"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Ne, hvala"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standardno"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Ekstremno"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Automatski zakreni zaslon"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Želite li dopustiti aplikaciji <xliff:g id="APPLICATION">%1$s</xliff:g> pristup uređaju <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Želite li dopustiti aplikaciji <xliff:g id="APPLICATION">%1$s</xliff:g> da pristupa uređaju <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nTa aplikacija nema dopuštenje za snimanje, no mogla bi primati zvuk putem tog USB uređaja."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatski ponovo uključi sutra"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Značajke kao što su brzo dijeljenje, Pronađi moj uređaj i lokacija uređaja upotrebljavaju Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Dodavanje još widgeta"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Dugo pritisnite za prilagodbu widgeta"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Prilagodi widgete"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikacije za onemogućeni widget"</string>
<string name="edit_widget" msgid="9030848101135393954">"Uredi widget"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Ukloni"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodaj widget"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Dodirnite da biste postavili na vibraciju."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Dodirnite da biste isključili zvuk."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Kontrola buke"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Dodirnite da biste promijenili način softvera zvona"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"isključivanje zvuka"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"uključivanje zvuka"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Izbornik tipke za uključivanje/isključivanje"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Stranica <xliff:g id="ID_1">%1$d</xliff:g> od <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Zaključani zaslon"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Telefon možete pronaći pomoću usluge Pronađi moj uređaj čak i kada je isključen"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Isključivanje…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Pročitajte upute za održavanje"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Pročitajte upute za održavanje"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Iskopčajte uređaj"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Za višu razlučivost okrenite telefon"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Rasklopljen sklopivi uređaj"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Okretanje sklopivog uređaja sa svih strana"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"zatvoreno"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"otvoreno"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Preostalo je <xliff:g id="PERCENTAGE">%s</xliff:g> baterije"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Priključite pisaljku na punjač"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Slaba baterija pisaljke"</string>
diff --git a/packages/SystemUI/res/values-hr/tiles_states_strings.xml b/packages/SystemUI/res/values-hr/tiles_states_strings.xml
index 217d999..75fb325 100644
--- a/packages/SystemUI/res/values-hr/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-hr/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Isključeno"</item>
<item msgid="578444932039713369">"Uključeno"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Nedostupno"</item>
+ <item msgid="9061144428113385092">"Isključeno"</item>
+ <item msgid="2984256114867200368">"Uključeno"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Nedostupno"</item>
<item msgid="8707481475312432575">"Isključeno"</item>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index b09419b..7a3af4f 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Bekapcsolás"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Bekapcsolás"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Most nem"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Normál"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extrém"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Képernyő automatikus forgatása"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Engedélyezi a(z) <xliff:g id="APPLICATION">%1$s</xliff:g> számára, hogy hozzáférjen a következőhöz: <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Lehetővé teszi a(z) <xliff:g id="APPLICATION">%1$s</xliff:g> alkalmazásnak, hogy hozzáférjen a következőhöz: <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nEz az alkalmazás nem rendelkezik rögzítési engedéllyel, de ezzel az USB-eszközzel képes a hangfelvételre."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatikus visszakapcsolás holnap"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Az olyan funkciók, mint a Quick Share, a Készülékkereső és az eszköz helyadatai Bluetootht használnak"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"További modulok hozzáadása"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Nyomja meg hosszan a modulok személyre szabásához"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Modulok személyre szabása"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Letiltott modul alkalmazásikonja"</string>
<string name="edit_widget" msgid="9030848101135393954">"Modul szerkesztése"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Eltávolítás"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Modul hozzáadása"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Koppintson a rezgés beállításához."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Koppintson a némításhoz."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Zajszabályozás"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Koppintson a csengés módjának módosításához"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"némítás"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"némítás feloldása"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Bekapcsológombhoz tartozó menü"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_1">%1$d</xliff:g>. oldal, összesen: <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Lezárási képernyő"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"A Készülékkereső segítségével akár a kikapcsolt telefon helyét is meghatározhatja."</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Leállítás…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Olvassa el a kímélő használat lépéseit"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Olvassa el a kímélő használat lépéseit"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Húzza ki az eszközt"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"A nagyobb felbontás érdekében fordítsa meg a telefont"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Összehajtható eszköz kihajtása"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Összehajtható eszköz körbeforgatása"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"összehajtva"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"kihajtva"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Akkumulátor töltöttségi szintje: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Tegye töltőre az érintőceruzát"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Az érintőceruza töltöttsége alacsony"</string>
diff --git a/packages/SystemUI/res/values-hu/tiles_states_strings.xml b/packages/SystemUI/res/values-hu/tiles_states_strings.xml
index fad2cd4..3ca3914 100644
--- a/packages/SystemUI/res/values-hu/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-hu/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Ki"</item>
<item msgid="578444932039713369">"Be"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Nem áll rendelkezésre"</item>
+ <item msgid="9061144428113385092">"Kikapcsolva"</item>
+ <item msgid="2984256114867200368">"Bekapcsolva"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Nem áll rendelkezésre"</item>
<item msgid="8707481475312432575">"Ki"</item>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 4ea86d0..d3b50d7 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Միացնել"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Միացնել"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Ոչ, շնորհակալություն"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Ստանդարտ"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Առավելագույն"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Ինքնապտտվող էկրան"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Թույլատրե՞լ <xliff:g id="APPLICATION">%1$s</xliff:g> հավելվածին օգտագործել <xliff:g id="USB_DEVICE">%2$s</xliff:g> լրասարքը։"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Թույլատրե՞լ <xliff:g id="APPLICATION">%1$s</xliff:g> հավելվածին օգտագործել <xliff:g id="USB_DEVICE">%2$s</xliff:g>ը։\nՀավելվածը ձայնագրելու թույլտվություն չունի, սակայն կկարողանա գրանցել ձայնն այս USB սարքի միջոցով։"</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Վաղը նորից ավտոմատ միացնել"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Գործառույթները, ինչպիսիք են Quick Share-ը, «Գտնել իմ սարքը» գործառույթը և սարքի տեղորոշումը, օգտագործում են Bluetooth-ը"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Ավելացնել վիջեթներ"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Երկար սեղմեք՝ վիջեթները հարմարեցնելու համար"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Հարմարեցնել վիջեթները"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Հավելվածի պատկերակ անջատված վիջեթի համար"</string>
<string name="edit_widget" msgid="9030848101135393954">"Փոփոխել վիջեթը"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Հեռացնել"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ավելացնել վիջեթ"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s։ Հպեք՝ թրթռոցը միացնելու համար։"</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s։ Հպեք՝ ձայնը անջատելու համար։"</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Աղմուկի կառավարում"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Հպեք՝ զանգակի ռեժիմը փոխելու համար"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"անջատել ձայնը"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"միացնել ձայնը"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Սնուցման կոճակի ընտրացանկ"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Էջ <xliff:g id="ID_1">%1$d</xliff:g> / <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Կողպէկրան"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"«Գտնել իմ սարքը» ծառայության օգնությամբ դուք կարող եք տեղորոշել այս հեռախոսը, նույնիսկ եթե այն անջատված է"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Անջատվում է…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Քայլեր գերտաքացման ահազանգի դեպքում"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Քայլեր գերտաքացման ահազանգի դեպքում"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Անջատեք սարքը"</string>
@@ -1226,6 +1236,12 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Ավելի մեծ լուծաչափի համար շրջեք հեռախոսը"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Ծալովի սարք՝ բացված վիճակում"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Ծալովի սարք՝ շրջված վիճակում"</string>
+ <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+ <skip />
+ <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+ <skip />
+ <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+ <skip />
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Մարտկոցի լիցքը՝ <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Ձեր ստիլուսը միացրեք լիցքավորիչի"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Ստիլուսի մարտկոցի լիցքի ցածր մակարդակ"</string>
diff --git a/packages/SystemUI/res/values-hy/tiles_states_strings.xml b/packages/SystemUI/res/values-hy/tiles_states_strings.xml
index 380d9d2..89a94e8 100644
--- a/packages/SystemUI/res/values-hy/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-hy/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Անջատված է"</item>
<item msgid="578444932039713369">"Միացված է"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Հասանելի չէ"</item>
+ <item msgid="9061144428113385092">"Անջատված է"</item>
+ <item msgid="2984256114867200368">"Միացված է"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Հասանելի չէ"</item>
<item msgid="8707481475312432575">"Անջատված է"</item>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 188f3fb..3e34e5f 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Aktifkan"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Aktifkan"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Lain kali"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standar"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Ekstrem"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Putar layar otomatis"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Izinkan <xliff:g id="APPLICATION">%1$s</xliff:g> mengakses <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Izinkan <xliff:g id="APPLICATION">%1$s</xliff:g> mengakses <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nAplikasi ini belum diberi izin merekam, tetapi dapat merekam audio melalui perangkat USB ini."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Otomatis aktifkan lagi besok"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Fitur seperti Quick Share, Temukan Perangkat Saya, dan lokasi perangkat menggunakan Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Tambahkan widget lainnya"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Tekan lama untuk menyesuaikan widget"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Sesuaikan widget"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikon aplikasi untuk widget yang dinonaktifkan"</string>
<string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Hapus"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Tambahkan widget"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Ketuk untuk menyetel agar bergetar."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Ketuk untuk menonaktifkan."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Kontrol Bising"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Ketuk untuk mengubah mode pendering"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"Tanpa suara"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"aktifkan"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menu daya"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Halaman <xliff:g id="ID_1">%1$d</xliff:g> dari <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Layar kunci"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Anda dapat menemukan lokasi ponsel ini dengan Temukan Perangkat Saya meskipun ponsel dimatikan"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Sedang mematikan…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Lihat langkah-langkah perawatan"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Lihat langkah-langkah perawatan"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Cabut perangkat"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Untuk resolusi lebih tinggi, balik ponsel"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Perangkat foldable sedang dibentangkan"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Perangkat foldable sedang dibalik"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ditutup"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"dibuka"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Baterai tersisa <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Hubungkan stilus ke pengisi daya"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Baterai stilus lemah"</string>
diff --git a/packages/SystemUI/res/values-in/tiles_states_strings.xml b/packages/SystemUI/res/values-in/tiles_states_strings.xml
index 9be5d02..e1d5338 100644
--- a/packages/SystemUI/res/values-in/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-in/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Nonaktif"</item>
<item msgid="578444932039713369">"Aktif"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Tidak tersedia"</item>
+ <item msgid="9061144428113385092">"Nonaktif"</item>
+ <item msgid="2984256114867200368">"Aktif"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Tidak tersedia"</item>
<item msgid="8707481475312432575">"Nonaktif"</item>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 47756c2..a707d36 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Kveikja"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Kveikja"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Nei, takk"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Staðlað"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Mikill"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Snúa skjá sjálfkrafa"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Viltu veita <xliff:g id="APPLICATION">%1$s</xliff:g> aðgang að <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Viltu veita <xliff:g id="APPLICATION">%1$s</xliff:g> aðgang að <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nÞetta forrit hefur ekki fengið heimild fyrir upptöku en gæti tekið upp hljóð í gegnum þetta USB-tæki."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Kveikja sjálfkrafa aftur á morgun"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Eiginleikar á borð við flýtideilingu, „Finna tækið mitt“ og staðsetningu tækis nota Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Bæta við fleiri græjum"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Haltu inni til að sérsníða græjur"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Sérsníða græjur"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Forritstákn fyrir græju sem slökkt er á"</string>
<string name="edit_widget" msgid="9030848101135393954">"Breyta græju"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Fjarlægja"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Bæta græju við"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Ýttu til að stilla á titring."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Ýttu til að þagga."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Hávaðavörn"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Ýta til að skipta um hringjarastillingu"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"þagga"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"hætta að þagga"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Aflrofavalmynd"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Blaðsíða <xliff:g id="ID_1">%1$d</xliff:g> af <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Lásskjár"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Þú getur fundið þennan síma með „Finna tækið mitt“, jafnvel þótt slökkt sé á honum"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Slekkur…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Sjá varúðarskref"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Sjá varúðarskref"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Taktu tækið úr sambandi"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Snúðu símanum til að fá betri upplausn"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Samanbrjótanlegt tæki opnað"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Samanbrjótanlegu tæki snúið við"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"samanbrotið"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"opið"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> hleðsla eftir á rafhlöðu"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Tengdu pennann við hleðslutæki"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Rafhlaða pennans er að tæmast"</string>
diff --git a/packages/SystemUI/res/values-is/tiles_states_strings.xml b/packages/SystemUI/res/values-is/tiles_states_strings.xml
index 1ee6e47..1bd38ba 100644
--- a/packages/SystemUI/res/values-is/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-is/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Slökkt"</item>
<item msgid="578444932039713369">"Kveikt"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Ekki tiltækt"</item>
+ <item msgid="9061144428113385092">"Slökkt"</item>
+ <item msgid="2984256114867200368">"Kveikt"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Ekki í boði"</item>
<item msgid="8707481475312432575">"Slökkt"</item>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 5bad44c..dc95717 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Attiva"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Attiva"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"No, grazie"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Estremo"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Rotazione automatica schermo"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Consentire a <xliff:g id="APPLICATION">%1$s</xliff:g> di accedere a <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Vuoi consentire all\'app <xliff:g id="APPLICATION">%1$s</xliff:g> di accedere a <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nA questa app non è stata concessa l\'autorizzazione di registrazione, ma l\'app potrebbe acquisire l\'audio tramite questo dispositivo USB."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Riattiva automaticamente domani"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funzionalità come Quick Share, Trova il mio dispositivo e la posizione del dispositivo usano il Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Aggiungi altri widget"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Premi a lungo per personalizzare i widget"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizza widget"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icona dell\'app per widget disattivati"</string>
<string name="edit_widget" msgid="9030848101135393954">"Modifica widget"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Rimuovi"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Aggiungi widget"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Tocca per attivare la vibrazione."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Tocca per disattivare l\'audio."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Controllo del rumore"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Tocca per cambiare la modalità della suoneria"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"silenzia"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"riattiva l\'audio"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menu del tasto di accensione"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Pagina <xliff:g id="ID_1">%1$d</xliff:g> di <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Schermata di blocco"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Puoi trovare questo smartphone tramite Trova il mio dispositivo anche quando è spento"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Arresto in corso…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Leggi le misure da adottare"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Leggi le misure da adottare"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Scollega il dispositivo"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Gira il telefono per una maggiore risoluzione"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo pieghevole che viene aperto"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo pieghevole che viene capovolto"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"Piegato"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"Non piegato"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> batteria rimanente"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Connetti lo stilo a un caricabatterie"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Batteria stilo in esaurimento"</string>
diff --git a/packages/SystemUI/res/values-it/tiles_states_strings.xml b/packages/SystemUI/res/values-it/tiles_states_strings.xml
index 28e28ae..f7abea5 100644
--- a/packages/SystemUI/res/values-it/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-it/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Off"</item>
<item msgid="578444932039713369">"On"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Non disponibile"</item>
+ <item msgid="9061144428113385092">"Off"</item>
+ <item msgid="2984256114867200368">"On"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Non disponibile"</item>
<item msgid="8707481475312432575">"Off"</item>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 154de15..f88b104 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"הפעלה"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"הפעלה"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"לא תודה"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"רגיל"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"משמעותי"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"סיבוב אוטומטי של המסך"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"לתת לאפליקציה <xliff:g id="APPLICATION">%1$s</xliff:g> גישה אל <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"האם לאפשר לאפליקציה <xliff:g id="APPLICATION">%1$s</xliff:g> גישה אל <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nאפליקציה זו לא קיבלה הרשאה להקליט אך יכולה לתעד אודיו באמצעות מכשיר USB זה."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"החיבור יופעל שוב אוטומטית מחר"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"תכונות כמו \'שיתוף מהיר\', \'איפה המכשיר שלי\' ומיקום המכשיר משתמשות בחיבור Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"הוספת ווידג\'טים"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"לוחצים לחיצה ארוכה כדי להתאים אישית את הווידג\'טים"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"התאמה אישית של ווידג\'טים"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"סמל האפליקציה לווידג\'ט שהושבת"</string>
<string name="edit_widget" msgid="9030848101135393954">"עריכת הווידג\'ט"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"הסרה"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"הוספת ווידג\'ט"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. יש להקיש כדי להעביר למצב רטט."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. יש להקיש כדי להשתיק."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"בקרת הרעש"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"יש להקיש כדי לשנות את מצב תוכנת הצלצול"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"השתקה"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"ביטול ההשתקה"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"תפריט הפעלה"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"דף <xliff:g id="ID_1">%1$d</xliff:g> מתוך <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"מסך נעילה"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"אפשר לאתר את הטלפון הזה עם שירות \'איפה המכשיר שלי\' גם כשהוא כבוי"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"בתהליך כיבוי…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"לצפייה בשלבי הטיפול"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"לצפייה בשלבי הטיפול"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"ניתוק המכשיר"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"כדי לצלם תמונה ברזולוציה גבוהה יותר, כדאי להפוך את הטלפון"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"מכשיר מתקפל עובר למצב לא מקופל"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"מכשיר מתקפל עובר למצב מהופך"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"מצב מקופל"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"מצב לא מקופל"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"רמת הטעינה שנותרה בסוללה: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"כדאי לחבר את הסטיילוס למטען"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"הסוללה של הסטיילוס חלשה"</string>
diff --git a/packages/SystemUI/res/values-iw/tiles_states_strings.xml b/packages/SystemUI/res/values-iw/tiles_states_strings.xml
index bb3eb10..1948685 100644
--- a/packages/SystemUI/res/values-iw/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-iw/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"כבוי"</item>
<item msgid="578444932039713369">"פועל"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"לא זמין"</item>
+ <item msgid="9061144428113385092">"מצב מושבת"</item>
+ <item msgid="2984256114867200368">"מצב פעיל"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"לא זמין"</item>
<item msgid="8707481475312432575">"כבוי"</item>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 227edd3..36fc26f 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"ONにする"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"ON にする"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"いいえ"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"標準"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"スーパー"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"自動回転画面"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> に <xliff:g id="USB_DEVICE">%2$s</xliff:g> へのアクセスを許可しますか?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g> に <xliff:g id="USB_DEVICE">%2$s</xliff:g>へのアクセスを許可しますか?\nこのアプリに録音権限は付与されていませんが、アクセスを許可すると、この USB デバイスから音声を収集できるようになります。"</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"明日自動的に ON に戻す"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"クイック共有、デバイスを探す、デバイスの位置情報などの機能は Bluetooth を使用します"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -582,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s。タップしてバイブレーションに設定します。"</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s。タップしてミュートします。"</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"ノイズ コントロール"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"タップすると、着信音のモードを変更できます"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"ミュート"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"ミュートを解除"</string>
@@ -1223,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"高解像度で撮るにはスマートフォンを裏返してください"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"折りたたみ式デバイスが広げられている"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"折りたたみ式デバイスがひっくり返されている"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"折りたたんだ状態"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"広げた状態"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"バッテリー残量 <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"タッチペンを充電器に接続してください"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"タッチペンのバッテリー残量が少なくなっています"</string>
diff --git a/packages/SystemUI/res/values-ja/tiles_states_strings.xml b/packages/SystemUI/res/values-ja/tiles_states_strings.xml
index ebadf3b..dd78c5e 100644
--- a/packages/SystemUI/res/values-ja/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-ja/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"OFF"</item>
<item msgid="578444932039713369">"ON"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"使用不可"</item>
+ <item msgid="9061144428113385092">"OFF"</item>
+ <item msgid="2984256114867200368">"ON"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"使用不可"</item>
<item msgid="8707481475312432575">"OFF"</item>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 70eeb33..e77d137 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"ჩართვა"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"ჩართვა"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"არა, გმადლობთ"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"სტანდარტული"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"უკიდურესი"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"ეკრანის ავტოროტაცია"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"მიეცეს <xliff:g id="APPLICATION">%1$s</xliff:g>-ს <xliff:g id="USB_DEVICE">%2$s</xliff:g>-ზე წვდომის უფლება?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"დართავთ <xliff:g id="APPLICATION">%1$s</xliff:g>-ს <xliff:g id="USB_DEVICE">%2$s</xliff:g>-ზე წვდომის ნებას?\nამ აპს არ აქვს მინიჭებული ჩაწერის ნებართვა, მაგრამ შეუძლია ჩაიწეროს აუდიო ამ USB მოწყობილობის მეშვეობით."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"ხელახლა ავტომატურად ჩართვა ხვალ"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"ფუნქციები, როგორებიცაა „სწრაფი გაზიარება“, „ჩემი მოწყობილობის პოვნა“ და „მოწყობილობის მდებარეობა“ იყენებენ Bluetooth-ს"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"ვიჯეტების დამატება"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ხანგრძლივად დააჭირეთ ვიჯეტების მოსარგებად"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ვიჯეტების მორგება"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"აპის ხატულა გათიშული ვიჯეტისთვის"</string>
<string name="edit_widget" msgid="9030848101135393954">"ვიჯეტის რედაქტირება"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"ამოშლა"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ვიჯეტის დამატება"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. შეეხეთ ვიბრაციაზე დასაყენებლად."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. შეეხეთ დასადუმებლად."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"ხმაურის კონტროლი"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"შეეხეთ მრეკავის რეჟიმის შესაცვლელად"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"დადუმება"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"დადუმების მოხსნა"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"ჩართვის მენიუ"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"გვერდი <xliff:g id="ID_1">%1$d</xliff:g> / <xliff:g id="ID_2">%2$d</xliff:g>-დან"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"ჩაკეტილი ეკრანი"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"შეგიძლიათ დაადგინოთ ამ ტელეფონის მდებარეობა ფუნქციით „ჩემი მოწყობილობის პოვნა“, მაშინაც კი, როდესაც ის გამორთულია"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"მიმდინარეობს გამორთვა…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"მისაღები ზომების გაცნობა"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"მისაღები ზომების გაცნობა"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"გამოაერᲗეᲗ Თქვენი მოწყობილობა"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"მაღალი გარჩევადობისთვის ამოაბრუნეთ ტელეფონი"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"დასაკეცი მოწყობილობა იხსნება"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"დასაკეცი მოწყობილობა ტრიალებს"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"დაკეცილი"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"გაშლილი"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"დარჩენილია ბატარეის <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"დააკავშირეთ თქვენი სტილუსი დამტენს"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"სტილუსის ბატარეა დაცლის პირასაა"</string>
diff --git a/packages/SystemUI/res/values-ka/tiles_states_strings.xml b/packages/SystemUI/res/values-ka/tiles_states_strings.xml
index 07a8a76..2691b69 100644
--- a/packages/SystemUI/res/values-ka/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-ka/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"გამორთულია"</item>
<item msgid="578444932039713369">"ჩართულია"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"მიუწვდომელია"</item>
+ <item msgid="9061144428113385092">"გამორთული"</item>
+ <item msgid="2984256114867200368">"ჩართული"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"მიუწვდომელია"</item>
<item msgid="8707481475312432575">"გამორთულია"</item>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index c2525d9..6daba135 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Қосу"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Қосу"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Жоқ, рақмет"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Стандартты"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Барынша"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Авто айналатын экран"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> қолданбасына <xliff:g id="USB_DEVICE">%2$s</xliff:g> құрылғысына кіруге рұқсат берілсін бе?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g> қолданбасына <xliff:g id="USB_DEVICE">%2$s</xliff:g> құрылғысын пайдалануға рұқсат етілсін бе?\nҚолданбаның жазу рұқсаты жоқ, бірақ осы USB құрылғысы арқылы аудио жаза алады."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Ертең автоматты түрде қосылсын"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Quick Share, Find My Device сияқты функциялар мен құрылғы локациясы Bluetooth пайдаланады."</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Басқа виджеттер қосыңыз."</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Виджеттерді бейімдеу үшін ұзақ басып тұрыңыз."</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Виджеттерді реттеу"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Өшірілген виджеттің қолданба белгішесі"</string>
<string name="edit_widget" msgid="9030848101135393954">"Виджетті өзгерту"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Өшіру"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Виджет қосу"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Діріл режимін орнату үшін түртіңіз."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Дыбысын өшіру үшін түртіңіз."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Шуды реттеу"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Қоңырау режимін өзгерту үшін түртіңіз."</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"дыбысын өшіру"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"дыбысын қосу"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Қуат мәзірі"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g> ішінен <xliff:g id="ID_1">%1$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Құлыптаулы экран"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Сіз бұл телефонды, ол тіпті өшірулі тұрса да Find My Device арқылы таба аласыз."</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Өшіріліп жатыр…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Пайдалану нұсқаулығын қараңыз"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Пайдалану нұсқаулығын қараңыз"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Құрылғыны ажыратыңыз"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Жоғары ажыратымдылық үшін телефонды айналдырыңыз."</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Бүктемелі құрылғы ашылып жатыр."</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Бүктемелі құрылғы аударылып жатыр."</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"жабық"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"ашық"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Қалған батарея заряды: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Стилусты зарядтағышқа жалғаңыз."</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Стилус батареясының заряды аз"</string>
diff --git a/packages/SystemUI/res/values-kk/tiles_states_strings.xml b/packages/SystemUI/res/values-kk/tiles_states_strings.xml
index f5b0948..b37e982 100644
--- a/packages/SystemUI/res/values-kk/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-kk/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Өшірулі"</item>
<item msgid="578444932039713369">"Қосулы"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Қолжетімді емес"</item>
+ <item msgid="9061144428113385092">"Өшірулі"</item>
+ <item msgid="2984256114867200368">"Қосулы"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Қолжетімсіз"</item>
<item msgid="8707481475312432575">"Өшірулі"</item>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 0d25033..69adb22 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"បើក"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"បើក"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"ទេ អរគុណ"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"ស្តង់ដារ"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"ខ្លាំងបំផុត"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"បង្វិលអេក្រង់ស្វ័យប្រវត្តិ"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"អនុញ្ញាត <xliff:g id="APPLICATION">%1$s</xliff:g> ឱ្យចូលប្រើ <xliff:g id="USB_DEVICE">%2$s</xliff:g> មែនទេ?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"អនុញ្ញាតឱ្យ <xliff:g id="APPLICATION">%1$s</xliff:g> ចូលប្រើ <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nកម្មវិធីនេះមិនទាន់បានទទួលសិទ្ធិថតសំឡេងនៅឡើយទេ ប៉ុន្តែអាចថតសំឡេងតាមរយៈឧបករណ៍ USB នេះបាន។"</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"បើកដោយស្វ័យប្រវត្តិម្ដងទៀតនៅថ្ងៃស្អែក"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"មុខងារដូចជា Quick Share, រកឧបករណ៍របស់ខ្ញុំ និងប៊្លូធូសប្រើប្រាស់ទីតាំងឧបករណ៍"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"បញ្ចូលធាតុក្រាហ្វិកច្រើនទៀត"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ចុចឱ្យយូរ ដើម្បីប្ដូរធាតុក្រាហ្វិកតាមបំណង"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ប្ដូរធាតុក្រាហ្វិកតាមបំណង"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"រូបកម្មវិធីសម្រាប់ធាតុក្រាហ្វិកដែលបានបិទ"</string>
<string name="edit_widget" msgid="9030848101135393954">"កែធាតុក្រាហ្វិក"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"ដកចេញ"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"បញ្ចូលធាតុក្រាហ្វិក"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s ។ ចុចដើម្បីកំណត់ឲ្យញ័រ។"</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s ។ ចុចដើម្បីបិទសំឡេង។"</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"ការគ្រប់គ្រងសំឡេងរំខាន"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"ចុចដើម្បីប្ដូរមុខងាររោទ៍"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"បិទសំឡេង"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"បើកសំឡេង"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"ម៉ឺនុយថាមពល"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"ទំព័រ <xliff:g id="ID_1">%1$d</xliff:g> នៃ <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"អេក្រង់ចាក់សោ"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"អ្នកអាចកំណត់ទីតាំងទូរសព្ទនេះដោយប្រើ \"រកឧបករណ៍របស់ខ្ញុំ\" សូម្បីនៅពេលបិទថាមពល"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"កំពុងបិទ…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"មើលជំហានថែទាំ"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"មើលជំហានថែទាំ"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"ដកឧបករណ៍របស់អ្នក"</string>
@@ -1226,6 +1236,12 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"សម្រាប់កម្រិតគុណភាពកាន់តែខ្ពស់ សូមត្រឡប់ទូរសព្ទ"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ឧបករណ៍អាចបត់បានកំពុងត្រូវបានលា"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ឧបករណ៍អាចបត់បានកំពុងត្រូវបានលា"</string>
+ <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+ <skip />
+ <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+ <skip />
+ <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+ <skip />
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"ថ្មនៅសល់ <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ភ្ជាប់ប៊ិករបស់អ្នកជាមួយឆ្នាំងសាក"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"ថ្មប៊ិកនៅសល់តិច"</string>
diff --git a/packages/SystemUI/res/values-km/tiles_states_strings.xml b/packages/SystemUI/res/values-km/tiles_states_strings.xml
index a2031b0..0b2d5b3 100644
--- a/packages/SystemUI/res/values-km/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-km/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"បិទ"</item>
<item msgid="578444932039713369">"បើក"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"មិនអាចប្រើបាន"</item>
+ <item msgid="9061144428113385092">"បិទ"</item>
+ <item msgid="2984256114867200368">"បើក"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"មិនមានទេ"</item>
<item msgid="8707481475312432575">"បិទ"</item>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 66b8e72..f62c2a6 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"ಆನ್ ಮಾಡಿ"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"ಆನ್ ಮಾಡಿ"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"ಬೇಡ"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"ಪ್ರಮಾಣಿತ"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"ತೀವ್ರ"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"ಪರದೆಯನ್ನು ಸ್ವಯಂ-ತಿರುಗಿಸಿ"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> ಪ್ರವೇಶಿಸಲು <xliff:g id="APPLICATION">%1$s</xliff:g> ಅನ್ನು ಅನುಮತಿಸುವುದೇ?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> ಅನ್ನು ಪ್ರವೇಶಿಸಲು <xliff:g id="APPLICATION">%1$s</xliff:g> ಅನ್ನು ಅನುಮತಿಸುವುದೇ?\nಈ ಆ್ಯಪ್ಗೆ ರೆಕಾರ್ಡ್ ಅನುಮತಿಯನ್ನು ನೀಡಲಾಗಿಲ್ಲ, ಆದರೆ ಈ USB ಸಾಧನದ ಮೂಲಕ ಆಡಿಯೊವನ್ನು ಸೆರೆಹಿಡಿಯಬಹುದು."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"ನಾಳೆ ಪುನಃ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಆನ್ ಮಾಡಿ"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"ಕ್ವಿಕ್ ಶೇರ್, Find My Device ನಂತಹ ಫೀಚರ್ಗಳು ಮತ್ತು ಸಾಧನದ ಸ್ಥಳ ಬ್ಲೂಟೂತ್ ಬಳಸುತ್ತವೆ"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"ಹೆಚ್ಚಿನ ವಿಜೆಟ್ಗಳನ್ನು ಸೇರಿಸಿ"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ವಿಜೆಟ್ಗಳನ್ನು ಕಸ್ಟಮೈಸ್ ಮಾಡಲು ದೀರ್ಘಕಾಲ ಒತ್ತಿರಿ"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ವಿಜೆಟ್ಗಳನ್ನು ಕಸ್ಟಮೈಸ್ ಮಾಡಿ"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾದ ವಿಜೆಟ್ಗಾಗಿ ಆ್ಯಪ್ ಐಕಾನ್"</string>
<string name="edit_widget" msgid="9030848101135393954">"ವಿಜೆಟ್ ಅನ್ನು ಎಡಿಟ್ ಮಾಡಿ"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"ತೆಗೆದುಹಾಕಿ"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ವಿಜೆಟ್ ಅನ್ನು ಸೇರಿಸಿ"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. ವೈಬ್ರೇಟ್ ಮಾಡಲು ಹೊಂದಿಸುವುದಕ್ಕಾಗಿ ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. ಮ್ಯೂಟ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"ಗದ್ದಲ ನಿಯಂತ್ರಣ"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"ರಿಂಗರ್ ಮೋಡ್ ಬದಲಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"ಮ್ಯೂಟ್ ಮಾಡಿ"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"ಅನ್ಮ್ಯೂಟ್ ಮಾಡಿ"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"ಪವರ್ ಮೆನು"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g> ರಲ್ಲಿ <xliff:g id="ID_1">%1$d</xliff:g> ಪುಟ"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"ಲಾಕ್ ಪರದೆ"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"ಪವರ್ ಆಫ್ ಆಗಿರುವಾಗಲೂ ನೀವು Find My Device ಮೂಲಕ ಈ ಫೋನ್ ಅನ್ನು ಪತ್ತೆ ಮಾಡಬಹುದು"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"ಶಟ್ ಡೌನ್ ಮಾಡಲಾಗುತ್ತಿದೆ…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"ಕಾಳಜಿಯ ಹಂತಗಳನ್ನು ವೀಕ್ಷಿಸಿ"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ಕಾಳಜಿಯ ಹಂತಗಳನ್ನು ವೀಕ್ಷಿಸಿ"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"ನಿಮ್ಮ ಸಾಧನವನ್ನು ಅನ್ಪ್ಲಗ್ ಮಾಡಿ"</string>
@@ -1226,6 +1236,12 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ಅಧಿಕ ರೆಸಲ್ಯೂಷನ್ಗಾಗಿ, ಫೋನ್ ಅನ್ನು ಫ್ಲಿಪ್ ಮಾಡಿ"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ಫೋಲ್ಡ್ ಮಾಡಬಹುದಾದ ಸಾಧನವನ್ನು ಅನ್ಫೋಲ್ಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ಫೋಲ್ಡ್ ಮಾಡಬಹುದಾದ ಸಾಧನವನ್ನು ಸುತ್ತಲೂ ತಿರುಗಿಸಲಾಗುತ್ತಿದೆ"</string>
+ <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+ <skip />
+ <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+ <skip />
+ <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+ <skip />
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ಬ್ಯಾಟರಿ ಉಳಿದಿದೆ"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ನಿಮ್ಮ ಸ್ಟೈಲಸ್ ಅನ್ನು ಚಾರ್ಜರ್ಗೆ ಕನೆಕ್ಟ್ ಮಾಡಿ"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"ಸ್ಟೈಲಸ್ ಬ್ಯಾಟರಿ ಕಡಿಮೆಯಿದೆ"</string>
diff --git a/packages/SystemUI/res/values-kn/tiles_states_strings.xml b/packages/SystemUI/res/values-kn/tiles_states_strings.xml
index de0fcae..3ae6578 100644
--- a/packages/SystemUI/res/values-kn/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-kn/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"ಆಫ್"</item>
<item msgid="578444932039713369">"ಆನ್"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"ಲಭ್ಯವಿಲ್ಲ"</item>
+ <item msgid="9061144428113385092">"ಆಫ್"</item>
+ <item msgid="2984256114867200368">"ಆನ್"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"ಲಭ್ಯವಿಲ್ಲ"</item>
<item msgid="8707481475312432575">"ಆಫ್"</item>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 028c8cf..40fb618 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"사용"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"사용"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"사용 안함"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"표준"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"긴급"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"화면 자동 회전"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> 앱이 <xliff:g id="USB_DEVICE">%2$s</xliff:g>에 액세스하도록 허용하시겠습니까?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g>에서 <xliff:g id="USB_DEVICE">%2$s</xliff:g>에 액세스하도록 허용하시겠습니까?\n이 앱에는 녹음 권한이 부여되지 않았지만, 이 USB 기기를 통해 오디오를 녹음할 수 있습니다."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"내일 다시 자동으로 사용 설정"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Quick Share, 내 기기 찾기, 기기 위치 등의 기능에서 블루투스를 사용합니다."</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"더 많은 위젯 추가"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"위젯을 맞춤설정하려면 길게 누르기"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"위젯 맞춤설정"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"사용 중지된 위젯의 앱 아이콘"</string>
<string name="edit_widget" msgid="9030848101135393954">"위젯 수정"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"삭제"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"위젯 추가"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. 탭하여 진동으로 설정하세요."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. 탭하여 음소거로 설정하세요."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"소음 제어"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"탭하여 벨소리 장치 모드 변경"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"음소거"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"음소거 해제"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"전원 메뉴"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g>페이지 중 <xliff:g id="ID_1">%1$d</xliff:g>페이지"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"잠금 화면"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"전원이 꺼져 있을 때도 내 기기 찾기로 이 휴대전화를 찾을 수 있습니다."</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"종료 중…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"해결 방법 확인하기"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"해결 방법 확인하기"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"기기 분리하기"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"해상도를 높이려면 후면 카메라를 사용하세요."</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"폴더블 기기를 펼치는 모습"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"폴더블 기기를 뒤집는 모습"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"접은 상태"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"펼친 상태"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"배터리 <xliff:g id="PERCENTAGE">%s</xliff:g> 남음"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"스타일러스를 충전기에 연결하세요"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"스타일러스 배터리 부족"</string>
diff --git a/packages/SystemUI/res/values-ko/tiles_states_strings.xml b/packages/SystemUI/res/values-ko/tiles_states_strings.xml
index c9b2846..002efb2 100644
--- a/packages/SystemUI/res/values-ko/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-ko/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"꺼짐"</item>
<item msgid="578444932039713369">"켜짐"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"사용 불가"</item>
+ <item msgid="9061144428113385092">"사용 안함"</item>
+ <item msgid="2984256114867200368">"사용"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"이용 불가"</item>
<item msgid="8707481475312432575">"꺼짐"</item>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 0f00555..ef213ca 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Күйгүзүү"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Күйгүзүү"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Жок, рахмат"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Кадимки"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Кескин"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Экранды авто буруу"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> колдонмосу <xliff:g id="USB_DEVICE">%2$s</xliff:g> түзмөгүн колдоно берсинби?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g> колдонмосу <xliff:g id="USB_DEVICE">%2$s</xliff:g> түзмөгүн колдоно берсинби?\nБул колдонмого жаздырууга уруксат берилген эмес, бирок ушул USB түзмөгү аркылуу үндөрдү жаза алат."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Эртең автоматтык түрдө кайра күйгүзүү"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Тез Бөлүшүү, \"Түзмөгүм кайда?\" жана түзмөктүн турган жерин аныктоо сыяктуу функциялар Bluetooth\'ду колдонот"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Көбүрөөк виджеттерди кошуу"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Виджеттерди ыңгайлаштыруу үчүн кое бербей басып туруңуз"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Виджеттерди ыңгайлаштыруу"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Өчүрүлгөн виджет үчүн колдонмонун сүрөтчөсү"</string>
<string name="edit_widget" msgid="9030848101135393954">"Виджетти түзөтүү"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Өчүрүү"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Виджет кошуу"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Дирилдөөгө коюу үчүн басыңыз."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Үнүн өчүрүү үчүн басыңыз."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Ызы-чууну көзөмөлдөө"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Коңгуроо режимин өзгөртүү үчүн басыңыз"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"үнсүз"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"үнүн чыгаруу"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Кубат баскычынын менюсу"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g> ичинен <xliff:g id="ID_1">%1$d</xliff:g>-бет"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Кулпуланган экран"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Бул телефон өчүк болсо да, аны \"Түзмөгүм кайда?\" кызматы аркылуу таба аласыз"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Өчүрүлүүдө…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Тейлөө кадамдарын көрүңүз"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Тейлөө кадамдарын көрүңүз"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Түзмөктү сууруп коюңуз"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Жогорку дааналык үчүн телефондун арткы камерасын колдонуңуз"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Ачылып турган бүктөлмө түзмөк"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Оодарылып жаткан бүктөлмө түзмөк"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"бүктөлгөн"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"ачылган"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Батареянын кубаты: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Стилусту кубаттаңыз"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Стилустун батареясы отурайын деп калды"</string>
diff --git a/packages/SystemUI/res/values-ky/tiles_states_strings.xml b/packages/SystemUI/res/values-ky/tiles_states_strings.xml
index bc47e5a..bb03d0a 100644
--- a/packages/SystemUI/res/values-ky/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-ky/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Өчүк"</item>
<item msgid="578444932039713369">"Күйүк"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Жеткиликсиз"</item>
+ <item msgid="9061144428113385092">"Өчүк"</item>
+ <item msgid="2984256114867200368">"Күйүк"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Жеткиликсиз"</item>
<item msgid="8707481475312432575">"Өчүк"</item>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index db97655..9468502 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"ເປີດໃຊ້"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"ເປີດໃຊ້"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"ບໍ່, ຂອບໃຈ"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"ມາດຕະຖານ"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"ສຸດຂີດ"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"ໝຸນໜ້າຈໍອັດຕະໂນມັດ"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"ອະນຸຍາດໃຫ້ <xliff:g id="APPLICATION">%1$s</xliff:g> ເຂົ້າເຖິງ <xliff:g id="USB_DEVICE">%2$s</xliff:g> ໄດ້ບໍ?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"ອະນຸຍາດໃຫ້ <xliff:g id="APPLICATION">%1$s</xliff:g> ເຂົ້າເຖິງ <xliff:g id="USB_DEVICE">%2$s</xliff:g> ໄດ້ບໍ?\nແອັບນີ້ບໍ່ໄດ້ຮັບອະນຸາດໃຫ້ບັນທຶກໄດ້ແຕ່ສາມາດບັນທຶກສຽງໄດ້ຜ່ານອຸປະກອນ USB ນີ້."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"ເປີດໃຊ້ໂດຍອັດຕະໂນມັດອີກຄັ້ງມື້ອື່ນ"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"ຄຸນສົມບັດຕ່າງໆເຊັ່ນ: ການແຊຣ໌ດ່ວນ, ຊອກຫາອຸປະກອນຂອງຂ້ອຍ ແລະ ສະຖານທີ່ອຸປະກອນໃຊ້ Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -582,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. ແຕະເພື່ອຕັ້ງເປັນສັ່ນເຕືອນ."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. ແຕະເພື່ອປິດສຽງ."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"ການຄວບຄຸມສຽງລົບກວນ"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"ແຕະເພື່ອປ່ຽນໂໝດຣິງເກີ"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"ປິດສຽງ"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"ເຊົາປິດສຽງ"</string>
@@ -1223,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ເພື່ອຄວາມລະອຽດທີ່ສູງຂຶ້ນ, ໃຫ້ປີ້ນໂທລະສັບ"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ອຸປະກອນທີ່ພັບໄດ້ກຳລັງກາງອອກ"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ອຸປະກອນທີ່ພັກໄດ້ກຳລັງປີ້ນໄປມາ"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ພັບແລ້ວ"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"ກາງອອກແລ້ວ"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"ແບັດເຕີຣີເຫຼືອ <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ເຊື່ອມຕໍ່ປາກກາຂອງທ່ານກັບສາຍສາກ"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"ແບັດເຕີຣີປາກກາເຫຼືອໜ້ອຍ"</string>
diff --git a/packages/SystemUI/res/values-lo/tiles_states_strings.xml b/packages/SystemUI/res/values-lo/tiles_states_strings.xml
index 7595897..3c288fc 100644
--- a/packages/SystemUI/res/values-lo/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-lo/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"ປິດ"</item>
<item msgid="578444932039713369">"ເປີດ"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"ບໍ່ພ້ອມໃຫ້ນຳໃຊ້"</item>
+ <item msgid="9061144428113385092">"ປິດ"</item>
+ <item msgid="2984256114867200368">"ເປີດ"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"ບໍ່ສາມາດໃຊ້ໄດ້"</item>
<item msgid="8707481475312432575">"ປິດ"</item>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 9eaab8f..3fc0dcb 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Įjungti"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Įjungti"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Ne, ačiū"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Įprasta"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Ekstremalus"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Automatiškai sukti ekraną"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Leisti „<xliff:g id="APPLICATION">%1$s</xliff:g>“ pasiekti įrenginį (<xliff:g id="USB_DEVICE">%2$s</xliff:g>)?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Leisti „<xliff:g id="APPLICATION">%1$s</xliff:g>“ pasiekti įrenginį (<xliff:g id="USB_DEVICE">%2$s</xliff:g>)?\nŠiai programai nebuvo suteiktas leidimas įrašyti, bet ji gali užfiksuoti garsą per šį USB įrenginį."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatiškai vėl įjungti rytoj"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Tokioms funkcijoms kaip „Spartusis bendrinimas“, „Rasti įrenginį“ ir įrenginio vietovė naudojamas „Bluetooth“ ryšys"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Pridėkite daugiau valdiklių"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Ilgai paspauskite, kad tinkintumėte valdiklius"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Tinkinti valdiklius"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Išjungto valdiklio programos piktograma"</string>
<string name="edit_widget" msgid="9030848101135393954">"Redaguoti valdiklį"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Pašalinti"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Pridėti valdiklį"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Palieskite, kad nustatytumėte vibravimą."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Palieskite, kad nutildytumėte."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Triukšmo valdymas"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Palieskite, kad pakeistumėte skambučio režimą"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"nutildyti"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"įjungti garsą"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Įjungimo meniu"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_1">%1$d</xliff:g> psl. iš <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Užrakinimo ekranas"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Šį telefoną galite rasti naudodami programą „Rasti įrenginį“, net jei jis išjungtas"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Išjungiama…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Žr. priežiūros veiksmus"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Žr. priežiūros veiksmus"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Atjunkite įrenginį"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Kad raiška būtų geresnė, apverskite telefoną"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Lankstomasis įrenginys išlankstomas"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Lankstomasis įrenginys apverčiamas"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"sulenkta"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"nesulenkta"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Liko akumuliatoriaus įkrovos: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Prijunkite rašiklį prie kroviklio"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Senka rašiklio akumuliatorius"</string>
diff --git a/packages/SystemUI/res/values-lt/tiles_states_strings.xml b/packages/SystemUI/res/values-lt/tiles_states_strings.xml
index 94343ba..8c4515b 100644
--- a/packages/SystemUI/res/values-lt/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-lt/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Išjungta"</item>
<item msgid="578444932039713369">"Įjungta"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Nepasiekiama"</item>
+ <item msgid="9061144428113385092">"Išjungta"</item>
+ <item msgid="2984256114867200368">"Įjungta"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Nepasiekiama"</item>
<item msgid="8707481475312432575">"Išjungta"</item>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index f69afb9..1a2f8ba 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Ieslēgt"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Ieslēgt"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Nē, paldies"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standarta"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Maks. taupīšana"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Automātiska ekrāna pagriešana"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Vai atļaut lietotnei <xliff:g id="APPLICATION">%1$s</xliff:g> piekļūt šai ierīcei: <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Vai atļaut lietotnei <xliff:g id="APPLICATION">%1$s</xliff:g> piekļūt ierīcei “<xliff:g id="USB_DEVICE">%2$s</xliff:g>”?\nŠai lietotnei nav piešķirta ierakstīšanas atļauja, taču tā varētu tvert audio, izmantojot šo USB ierīci."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automātiski atkal ieslēgt rīt"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Tādas funkcijas kā “Ātrā kopīgošana”, “Atrast ierīci” un ierīces atrašanās vietas noteikšana izmanto tehnoloģiju Bluetooth."</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Pievienot citus logrīkus"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Nospiediet un turiet, lai pielāgotu logrīkus."</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Pielāgot logrīkus"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Lietotnes ikona atspējotam logrīkam"</string>
<string name="edit_widget" msgid="9030848101135393954">"Rediģēt logrīku"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Noņemt"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Pievienot logrīku"</string>
@@ -575,16 +579,22 @@
<string name="volume_ringer_status_normal" msgid="1339039682222461143">"Zvanīt"</string>
<string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrēt"</string>
<string name="volume_ringer_status_silent" msgid="3691324657849880883">"Izslēgt skaņu"</string>
- <!-- no translation found for media_device_cast (4786241789687569892) -->
- <skip />
- <!-- no translation found for stream_notification_unavailable (4313854556205836435) -->
- <skip />
+ <string name="media_device_cast" msgid="4786241789687569892">"Apraide"</string>
+ <string name="stream_notification_unavailable" msgid="4313854556205836435">"Nevar mainīt, jo izslēgts skaņas signāls"</string>
<string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Pieskarieties, lai ieslēgtu skaņu."</string>
<string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Pieskarieties, lai iestatītu uz vibrozvanu. Var tikt izslēgti pieejamības pakalpojumu signāli."</string>
<string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Pieskarieties, lai izslēgtu skaņu. Var tikt izslēgti pieejamības pakalpojumu signāli."</string>
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Pieskarieties, lai iestatītu vibrozvanu."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Pieskarieties, lai izslēgtu skaņu."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Trokšņu kontrole"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Pieskarieties, lai mainītu zvanītāja režīmu."</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"izslēgt skaņu"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"ieslēgt skaņu"</string>
@@ -839,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Barošanas izvēlne"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_1">%1$d</xliff:g>. lpp. no <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Bloķēšanas ekrāns"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Lietotni “Atrast ierīci” var izmantot šī tālruņa atrašanās vietas noteikšanai arī tad, ja tālrunis ir izslēgts."</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Notiek izslēgšana…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Skatīt apkopes norādījumus"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Skatīt apkopes norādījumus"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Atvienojiet savu ierīci"</string>
@@ -1228,6 +1236,12 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Lai izmantotu augstāku izšķirtspēju, apvērsiet tālruni"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Salokāma ierīce tiek atlocīta"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Salokāma ierīce tiek apgriezta"</string>
+ <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+ <skip />
+ <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+ <skip />
+ <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+ <skip />
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Atlikušais uzlādes līmenis: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Pievienojiet skārienekrāna pildspalvu lādētājam"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Zems skārienekrāna pildspalvas akumulatora līmenis"</string>
diff --git a/packages/SystemUI/res/values-lv/tiles_states_strings.xml b/packages/SystemUI/res/values-lv/tiles_states_strings.xml
index d8b2467..a75e9d8 100644
--- a/packages/SystemUI/res/values-lv/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-lv/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Izslēgta"</item>
<item msgid="578444932039713369">"Ieslēgta"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Nav pieejams"</item>
+ <item msgid="9061144428113385092">"Izslēgts"</item>
+ <item msgid="2984256114867200368">"Ieslēgts"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Nav pieejama"</item>
<item msgid="8707481475312432575">"Izslēgta"</item>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 6ea4d1f..b6651ad 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Вклучи"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Вклучи"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Не, фала"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Стандарден"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Екстремен"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Автоматско ротирање на екранот"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Ќе дозволите <xliff:g id="APPLICATION">%1$s</xliff:g> да пристапува до <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Дали дозволувате <xliff:g id="APPLICATION">%1$s</xliff:g> да пристапи до <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nНа апликацијава не ѝ е доделена дозвола за снимање, но може да снима аудио преку овој USB-уред."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Автоматски вклучи повторно утре"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Функциите како „Брзо споделување“, „Најди го мојот уред“ и локација на уредот користат Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Додајте повеќе виџети"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Притиснете долго за да ги приспособите виџетите"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Приспособете ги виџетите"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Икона за апликација за оневозможен виџет"</string>
<string name="edit_widget" msgid="9030848101135393954">"Изменување виџети"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Отстранува"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Додајте виџет"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Допрете за да се постави на вибрации."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Допрете за да се исклучи звукот."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Контрола на бучавата"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Допрете за да го промените режимот на ѕвончето"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"исклучен звук"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"вклучен звук"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Мени на копчето за вклучување"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Страница <xliff:g id="ID_1">%1$d</xliff:g> од <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Заклучен екран"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Може да го лоцирате телефонов со „Најди го мојот уред“ дури и кога е исклучен"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Се исклучува…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Прикажи ги чекорите за грижа за уредот"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Прикажи ги чекорите за грижа за уредот"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Исклучете го уредот од кабел"</string>
@@ -1226,6 +1236,12 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Отворете го телефонот за да добиете повисока резолуција"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Преклопувачки уред се отклопува"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Преклопувачки уред се врти"</string>
+ <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+ <skip />
+ <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+ <skip />
+ <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+ <skip />
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Преостаната батерија: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Поврзете го пенкалото со полнач"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Слаба батерија на пенкало"</string>
diff --git a/packages/SystemUI/res/values-mk/tiles_states_strings.xml b/packages/SystemUI/res/values-mk/tiles_states_strings.xml
index 8b0faf7..4dd9e73 100644
--- a/packages/SystemUI/res/values-mk/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-mk/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Исклучено"</item>
<item msgid="578444932039713369">"Вклучено"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Недостапно"</item>
+ <item msgid="9061144428113385092">"Исклучено"</item>
+ <item msgid="2984256114867200368">"Вклучено"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Недостапно"</item>
<item msgid="8707481475312432575">"Исклучено"</item>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 82d257a..a48d530 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"ഓൺ ചെയ്യുക"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"ഓണാക്കുക"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"വേണ്ട"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"സ്റ്റാൻഡേർഡ്"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"എക്സ്ട്രീം"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"സ്ക്രീൻ സ്വയമേ തിരിയുക"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> ആക്സസ് ചെയ്യാൻ <xliff:g id="APPLICATION">%1$s</xliff:g>-നെ അനുവദിക്കണോ?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> ആക്സസ് ചെയ്യാൻ <xliff:g id="APPLICATION">%1$s</xliff:g> എന്നതിനെ അനുവദിക്കണോ?\nഈ ആപ്പിന് റെക്കോർഡ് അനുമതി നൽകിയിട്ടില്ല, എന്നാൽ ഈ USB ഉപകരണത്തിലൂടെ ഓഡിയോ ക്യാപ്ചർ ചെയ്യാനാവും."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"നാളെ വീണ്ടും സ്വയമേവ ഓണാക്കുക"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"ക്വിക്ക് ഷെയർ, Find My Device, ഉപകരണ ലൊക്കേഷൻ എന്നിവ പോലുള്ള ഫീച്ചറുകൾ Bluetooth ഉപയോഗിക്കുന്നു"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -582,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s വൈബ്രേറ്റിലേക്ക് സജ്ജമാക്കുന്നതിന് ടാപ്പുചെയ്യുക."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s മ്യൂട്ടുചെയ്യുന്നതിന് ടാപ്പുചെയ്യുക."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"നോയ്സ് നിയന്ത്രണം"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"റിംഗർ മോഡ് മാറ്റാൻ ടാപ്പ് ചെയ്യുക"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"മ്യൂട്ട് ചെയ്യുക"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"അൺമ്യൂട്ട് ചെയ്യുക"</string>
@@ -1223,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ഉയർന്ന റെസല്യൂഷന്, ഫോൺ ഫ്ലിപ്പ് ചെയ്യുക"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ഫോൾഡ് ചെയ്യാവുന്ന ഉപകരണം അൺഫോൾഡ് ആകുന്നു"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ഫോൾഡ് ചെയ്യാവുന്ന ഉപകരണം, കറങ്ങുന്ന വിധത്തിൽ ഫ്ലിപ്പ് ആകുന്നു"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ഫോൾഡ് ചെയ്തത്"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"അൺഫോൾഡ് ചെയ്തത്"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ബാറ്ററി ചാർജ് ശേഷിക്കുന്നു"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"നിങ്ങളുടെ സ്റ്റൈലസ് ചാർജറുമായി കണക്റ്റ് ചെയ്യുക"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"സ്റ്റൈലസിന്റെ ബാറ്ററി ചാർജ് കുറവാണ്"</string>
diff --git a/packages/SystemUI/res/values-ml/tiles_states_strings.xml b/packages/SystemUI/res/values-ml/tiles_states_strings.xml
index 529d0de..18e7b29 100644
--- a/packages/SystemUI/res/values-ml/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-ml/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"ഓഫാണ്"</item>
<item msgid="578444932039713369">"ഓണാണ്"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"ലഭ്യമല്ല"</item>
+ <item msgid="9061144428113385092">"ഓഫാണ്"</item>
+ <item msgid="2984256114867200368">"ഓണാണ്"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"ലഭ്യമല്ല"</item>
<item msgid="8707481475312432575">"ഓഫാണ്"</item>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index aad6a13..625b8b7 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Асаах"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Асаах"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Үгүй, баярлалаа"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Стандарт"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Экстрим"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Дэлгэцийг автоматаар эргүүлэх"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g>-г <xliff:g id="USB_DEVICE">%2$s</xliff:g>-д хандахыг зөвшөөрөх үү?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g>-д <xliff:g id="USB_DEVICE">%2$s</xliff:g>-д хандахыг зөвшөөрөх үү?\nЭнэ аппад бичих зөвшөөрөл олгогдоогүй ч USB төхөөрөмжөөр дамжуулан аудио бичиж чадсан."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Маргааш автоматаар дахин асаах"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Түргэн хуваалцах, Миний төхөөрөмжийг олох зэрэг онцлогууд болон төхөөрөмжийн байршил Bluetooth-г ашигладаг"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Илүү олон виджет нэмэх"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Виджетүүдийг өөрчлөхийн тулд удаан дарна уу"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Виджетүүдийг өөрчлөх"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Идэвхгүй болгосон виджетийн аппын дүрс тэмдэг"</string>
<string name="edit_widget" msgid="9030848101135393954">"Виджетийг засах"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Хасах"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Виджет нэмэх"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Чичиргээнд тохируулахын тулд товшино уу."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Дууг хаахын тулд товшино уу."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Шуугианы хяналт"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Хонхны горимыг өөрчлөхийн тулд товшино уу"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"дууг хаах"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"дууг нээх"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Асаах/унтраах цэс"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g>-н <xliff:g id="ID_1">%1$d</xliff:g>-р хуудас"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Түгжээтэй дэлгэц"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Та энэ утсыг унтраалттай байсан ч Миний төхөөрөмжийг олохоор байршлыг нь тогтоох боломжтой"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Унтрааж байна…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Хянамж болгоомжийн алхмыг харах"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Хянамж болгоомжийн алхмыг харах"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Төхөөрөмжөө салгана уу"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Илүү өндөр нягтрал авах бол утсыг хөнтөрнө үү"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Эвхэгддэг төхөөрөмжийг дэлгэж байна"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Эвхэгддэг төхөөрөмжийг хөнтөрч байна"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"эвхсэн"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"дэлгэсэн"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> батарей үлдлээ"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Мэдрэгч үзгээ цэнэглэгчтэй холбоорой"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Мэдрэгч үзэгний батарей бага байна"</string>
diff --git a/packages/SystemUI/res/values-mn/tiles_states_strings.xml b/packages/SystemUI/res/values-mn/tiles_states_strings.xml
index 0db1229..95f835e 100644
--- a/packages/SystemUI/res/values-mn/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-mn/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Унтраалттай"</item>
<item msgid="578444932039713369">"Асаалттай"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Боломжгүй"</item>
+ <item msgid="9061144428113385092">"Унтраалттай"</item>
+ <item msgid="2984256114867200368">"Асаалттай"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Боломжгүй"</item>
<item msgid="8707481475312432575">"Унтраалттай"</item>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index d7acf5f..b5c0dc7 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"सुरू करा"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"सुरू करा"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"नाही, नको"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"साधारण"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"एक्स्ट्रीम"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"ऑटो-रोटेट स्क्रीन"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> ला <xliff:g id="USB_DEVICE">%2$s</xliff:g> अॅक्सेस करण्याची अनुमती द्यायची का?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g> ला <xliff:g id="USB_DEVICE">%2$s</xliff:g> अॅक्सेस करण्याची अनुमती द्यायची का?\nया अॅपला रेकॉर्ड करण्याची परवानगी दिलेली नाही पण या USB डिव्हाइसद्वारे ऑडिओ कॅप्चर केला जाऊ शकतो."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"उद्या पुन्हा आपोआप सुरू करा"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Quick Share, Find My Device आणि डिव्हाइस स्थान यांसारखी वैशिष्ट्ये ब्लूटूथ वापरतात"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"आणखी विजेट जोडा"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"विजेट कस्टमाइझ करण्यासाठी प्रेस करून ठेवा"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"विजेट कस्टमाइझ करा"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"बंद केलेल्या विजेटच्या अॅपचे आयकन"</string>
<string name="edit_widget" msgid="9030848101135393954">"विजेट संपादित करा"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"काढून टाका"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"विजेट जोडा"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. व्हायब्रेट सेट करण्यासाठी टॅप करा."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. म्यूट करण्यासाठी टॅप करा."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"नॉइझ कंट्रोल"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"रिंगर मोड बदलण्यासाठी टॅप करा"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"म्यूट करा"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"म्यूट काढून टाका"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"पॉवर मेनू"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g> पैकी <xliff:g id="ID_1">%1$d</xliff:g> पेज"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"लॉक स्क्रीन"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"तुम्ही हा फोन बंद असतानादेखील Find My Device वापरून तो शोधू शकता"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"बंद होत आहे…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"काय काळजी घ्यावी ते पहा"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"काय काळजी घ्यावी ते पहा"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"तुमचे डिव्हाइस अनप्लग करा"</string>
@@ -1226,6 +1236,12 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"उच्च रेझोल्यूशनसाठी, फोन फ्लिप करा"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"फोल्ड करता येण्यासारखे डिव्हाइस अनफोल्ड केले जात आहे"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"फोल्ड करता येण्यासारखे डिव्हाइस आजूबाजूला फ्लिप केले जात आहे"</string>
+ <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+ <skip />
+ <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+ <skip />
+ <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+ <skip />
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> बॅटरी शिल्लक आहे"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"तुमचे स्टायलस चार्जरशी कनेक्ट करा"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"स्टायलस बॅटरी कमी आहे"</string>
diff --git a/packages/SystemUI/res/values-mr/tiles_states_strings.xml b/packages/SystemUI/res/values-mr/tiles_states_strings.xml
index b70a5cc..6902a2f 100644
--- a/packages/SystemUI/res/values-mr/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-mr/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"बंद आहे"</item>
<item msgid="578444932039713369">"सुरू आहे"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"उपलब्ध नाही"</item>
+ <item msgid="9061144428113385092">"बंद आहे"</item>
+ <item msgid="2984256114867200368">"सुरू आहे"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"उपलब्ध नाही"</item>
<item msgid="8707481475312432575">"बंद आहे"</item>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 03cd0a0..3419e63 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Hidupkan"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Hidupkan"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Tidak perlu"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Ekstrem"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Autoputar skrin"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Benarkan <xliff:g id="APPLICATION">%1$s</xliff:g> mengakses <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Benarkan <xliff:g id="APPLICATION">%1$s</xliff:g> mengakses <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nApl ini belum diberikan kebenaran merakam tetapi dapat merakam audio melalui peranti USB ini."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Dihidupkan sekali lagi esok secara automatik"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Ciri seperti Quick Share, Find My Device dan lokasi peranti menggunakan Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Tambahkan lagi widget"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Tekan lama untuk menyesuaikan widget"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Sesuaikan widget"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikon apl untuk melumpuhkan widget"</string>
<string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Alih keluar"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Tambahkan widget"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Ketik untuk menetapkan pada getar."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Ketik untuk meredam."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Kawalan Hingar"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Ketik untuk menukar mod pendering"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"redam"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"nyahredam"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menu kuasa"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Halaman <xliff:g id="ID_1">%1$d</xliff:g> daripada <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Kunci skrin"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Anda boleh mengesan telefon ini dengan Find My Device walaupun apabila telefon ini dimatikan kuasa"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Mematikan…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Lihat langkah penjagaan"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Lihat langkah penjagaan"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Cabut palam peranti anda"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Untuk peleraian lebih tinggi, balikkan telefon"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Peranti boleh lipat dibuka"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Peranti boleh lipat diterbalikkan"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"terlipat"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"tidak terlipat"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Bateri tinggal <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Sambungkan stilus anda kepada pengecas"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Bateri stilus lemah"</string>
diff --git a/packages/SystemUI/res/values-ms/tiles_states_strings.xml b/packages/SystemUI/res/values-ms/tiles_states_strings.xml
index b72a375..a280916 100644
--- a/packages/SystemUI/res/values-ms/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-ms/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Mati"</item>
<item msgid="578444932039713369">"Hidup"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Tidak tersedia"</item>
+ <item msgid="9061144428113385092">"Mati"</item>
+ <item msgid="2984256114867200368">"Hidup"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Tidak tersedia"</item>
<item msgid="8707481475312432575">"Mati"</item>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index c6d46bc..36984fc 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"ဖွင့်ရန်"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"ဖွင့်ရန်"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"မလိုပါ"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"ပုံမှန်"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"လွန်ကဲ"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"ဖန်သားပြင် အလိုအလျောက်လှည့်ရန်"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> အား ဝင်သုံးရန် <xliff:g id="APPLICATION">%1$s</xliff:g> ကို ခွင့်ပြုပါသလား။"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g> အား <xliff:g id="USB_DEVICE">%2$s</xliff:g> ကို သုံးခွင့်ပြုမလား။\nဤအက်ပ်ကို အသံဖမ်းခွင့် ပေးမထားသော်လည်း ၎င်းသည် ဤ USB စက်ပစ္စည်းမှတစ်ဆင့် အသံများကို ဖမ်းယူနိုင်ပါသည်။"</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"မနက်ဖြန် အလိုအလျောက် ထပ်ဖွင့်ရန်"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"‘အမြန် မျှဝေပါ’၊ Find My Device နှင့် စက်ပစ္စည်းတည်နေရာကဲ့သို့ တူးလ်များသည် ဘလူးတုသ်သုံးသည်"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"နောက်ထပ်ဝိဂျက်များ ထည့်ရန်"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ဝိဂျက်များ စိတ်ကြိုက်လုပ်ရန် ကြာကြာနှိပ်ထားပါ"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ဝိဂျက်များကို စိတ်ကြိုက်လုပ်ရန်"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ပိတ်ထားသော ဝိဂျက်အတွက် အက်ပ်သင်္ကေတ"</string>
<string name="edit_widget" msgid="9030848101135393954">"ဝိဂျက်ပြင်ရန်"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"ဖယ်ရှားရန်"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ဝိဂျက်ထည့်ရန်"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s။ တုန်ခါခြင်းသို့ သတ်မှတ်ရန်တို့ပါ။"</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s။ အသံပိတ်ရန် တို့ပါ။"</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"ဆူညံသံ ထိန်းချုပ်ရန်"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"ဖုန်းခေါ်သံမုဒ်သို့ ပြောင်းရန် တို့ပါ"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"အသံပိတ်ရန်"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"အသံဖွင့်ရန်"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"ပါဝါမီနူး"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"စာမျက်နှာ <xliff:g id="ID_2">%2$d</xliff:g> အနက်မှ စာမျက်နှာ <xliff:g id="ID_1">%1$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"လော့ခ်ချထားချိန် မျက်နှာပြင်"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"ပါဝါပိတ်ထားသော်လည်း Find My Device ဖြင့် ဤဖုန်းကို ရှာနိုင်သည်"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"စက်ပိတ်နေသည်…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"ဂရုပြုစရာ အဆင့်များ ကြည့်ရန်"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ဂရုပြုစရာ အဆင့်များ ကြည့်ရန်"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"သင့်စက်ကို ပလတ်ဖြုတ်ပါ"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ပုံရိပ် ပိုမိုပြတ်သားစေရန် ဖုန်းကို တစ်ဖက်သို့ လှန်လိုက်ပါ"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ခေါက်နိုင်သောစက်ကို ဖြန့်လိုက်သည်"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ခေါက်နိုင်သောစက်ကို တစ်ဘက်သို့ လှန်လိုက်သည်"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"ခေါက်ထားသည်"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"ဖြန့်ထားသည်"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"ဘက်ထရီ <xliff:g id="PERCENTAGE">%s</xliff:g> ကျန်သေးသည်"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"စတိုင်လပ်စ်ကို အားသွင်းကိရိယာနှင့် ချိတ်ဆက်ခြင်း"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"စတိုင်လပ်စ် ဘက်ထရီ အားနည်းနေသည်"</string>
diff --git a/packages/SystemUI/res/values-my/tiles_states_strings.xml b/packages/SystemUI/res/values-my/tiles_states_strings.xml
index d223dc9..ce10c42 100644
--- a/packages/SystemUI/res/values-my/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-my/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"ပိတ်"</item>
<item msgid="578444932039713369">"ဖွင့်"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"မရနိုင်ပါ"</item>
+ <item msgid="9061144428113385092">"ပိတ်"</item>
+ <item msgid="2984256114867200368">"ဖွင့်"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"မရနိုင်ပါ"</item>
<item msgid="8707481475312432575">"ပိတ်"</item>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index a2b97ee..e126dea 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Slå på"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Slå på"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Nei takk"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Ekstrem"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Rotér skjermen automatisk"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Vil du gi <xliff:g id="APPLICATION">%1$s</xliff:g> tilgang til <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Vil du gi <xliff:g id="APPLICATION">%1$s</xliff:g> tilgang til <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nDenne appen har ikke fått tillatelse til å spille inn, men kan ta opp lyd med denne USB-enheten."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Slå på igjen i morgen automatisk"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funksjoner som Quick Share, Finn enheten min og enhetsposisjon bruker Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Legg til flere moduler"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Trykk lenge for å tilpasse modulene"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Tilpass moduler"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Appikon for deaktivert modul"</string>
<string name="edit_widget" msgid="9030848101135393954">"Endre modul"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Fjern"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Legg til modul"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Trykk for å angi vibrasjon."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Trykk for å slå av lyden."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Støykontroll"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Trykk for å endre ringemodus"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"kutt lyden"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"slå på lyden"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Av/på-meny"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Side <xliff:g id="ID_1">%1$d</xliff:g> av <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Låseskjerm"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Du kan finne denne telefonen med Finn enheten min, selv når den er slått av"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Slår av …"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Se vedlikeholdstrinnene"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Se vedlikeholdstrinnene"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Koble fra enheten"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Brett ut telefonen for å få høyere oppløsning"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"En sammenleggbar enhet blir brettet ut"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"En sammenleggbar enhet blir snudd"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"lagt sammen"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"åpen"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> batteri gjenstår"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Koble pekepennen til en lader"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Det er lite batteri i pekepennen"</string>
diff --git a/packages/SystemUI/res/values-nb/tiles_states_strings.xml b/packages/SystemUI/res/values-nb/tiles_states_strings.xml
index 2ed0096..71160d0 100644
--- a/packages/SystemUI/res/values-nb/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-nb/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Av"</item>
<item msgid="578444932039713369">"På"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Ikke tilgjengelig"</item>
+ <item msgid="9061144428113385092">"Av"</item>
+ <item msgid="2984256114867200368">"På"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Utilgjengelig"</item>
<item msgid="8707481475312432575">"Av"</item>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 1388a85..a06c160 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"अन गर्नुहोस्"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"अन गर्नुहोस्"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"पर्दैन"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"डिफल्ट"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"चरम"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"स्वत:घुम्ने स्क्रिन"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> लाई <xliff:g id="USB_DEVICE">%2$s</xliff:g> माथि पहुँच राख्ने अनुमति दिने हो?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g> लाई <xliff:g id="USB_DEVICE">%2$s</xliff:g> माथि पहुँच राख्न अनुमति दिने हो?\nयो एपलाई रेकर्ड गर्ने अनुमति प्रदान गरिएको छैन तर यसले USB यन्त्रमार्फत अडियो क्याप्चर गर्न सक्छ।"</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"भोलि फेरि स्वतः अन गरियोस्"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"क्विक सेयर, Find My Device र डिभाइसको लोकेसन जस्ता सुविधाहरूले ब्लुटुथ प्रयोग गर्छन्"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"थप विजेटहरू हाल्नुहोस्"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"विजेटहरू कस्टमाइज गर्न केही बेरसम्म थिच्नुहोस्"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"विजेटहरू कस्टमाइज गर्नुहोस्"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"अफ गरिएको विजेटको एप जनाउने आइकन"</string>
<string name="edit_widget" msgid="9030848101135393954">"विजेट सम्पादन गर्नुहोस्"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"हटाउनुहोस्"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"विजेट हाल्नुहोस्"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s। कम्पन मोडमा सेट गर्न ट्याप गर्नुहोस्।"</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s। म्यूट गर्न ट्याप गर्नुहोस्।"</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"नोइज कन्ट्रोल"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"रिङ्गर मोड बदल्न ट्याप गर्नुहोस्"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"म्युट गर्नुहोस्"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"अनम्युट गर्नुहोस्"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"पावर मेनु"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g> मध्ये पृष्ठ <xliff:g id="ID_1">%1$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"लक स्क्रिन"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"तपाईं Find My Device प्रयोग गरी यो फोन अफ भए पनि यसको लोकेसन पत्ता लगाउन सक्नुहुन्छ"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"सट डाउन गरिँदै छ..."</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"डिभाइसको हेरचाह गर्ने तरिका हेर्नुहोस्"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"डिभाइसको हेरचाह गर्ने तरिका हेर्नुहोस्"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"डिभाइस बिजुलीको स्रोतबाट निकाल्नुहोस्"</string>
@@ -1226,6 +1236,12 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"उच्च रिजोल्युसनको सेल्फी खिच्न फोन फ्लिप गर्नुहोस्"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"फोल्ड गर्न मिल्ने डिभाइस अनफोल्ड गरेको देखाइएको एनिमेसन"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"फोल्ड गर्न मिल्ने डिभाइस यताउता पल्टाएर देखाइएको एनिमेसन"</string>
+ <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+ <skip />
+ <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+ <skip />
+ <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+ <skip />
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ब्याट्री बाँकी छ"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"आफ्नो स्टाइलस चार्जरमा कनेक्ट गर्नुहोस्"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"स्टाइलसको ब्याट्री लो छ"</string>
diff --git a/packages/SystemUI/res/values-ne/tiles_states_strings.xml b/packages/SystemUI/res/values-ne/tiles_states_strings.xml
index 40159fb..1977706 100644
--- a/packages/SystemUI/res/values-ne/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-ne/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"अफ छ"</item>
<item msgid="578444932039713369">"अन छ"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"उपलब्ध छैन"</item>
+ <item msgid="9061144428113385092">"अफ छ"</item>
+ <item msgid="2984256114867200368">"अन छ"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"उपलब्ध छैन"</item>
<item msgid="8707481475312432575">"अफ छ"</item>
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index b6971d3..291f8a2 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -53,6 +53,11 @@
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowLightStatusBar">false</item>
+
+ <!--
+ TODO(b/309578419): Make activities handle insets properly and then remove this.
+ -->
+ <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
<style name="TextAppearance.InternetDialog.Active">
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 09156cf..86b2ebe 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Aanzetten"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Aanzetten"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Nee, bedankt"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standaard"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extreem"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Scherm automatisch draaien"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> toegang geven tot <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g> toegang geven tot <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nDeze app heeft geen opnamerechten gekregen, maar zou audio kunnen vastleggen via dit USB-apparaat."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Morgen weer automatisch aanzetten"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Functies zoals Quick Share, Vind mijn apparaat en apparaatlocatie maken gebruik van bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Meer widgets toevoegen"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Houd lang ingedrukt om widgets aan te passen"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Widgets aanpassen"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App-icoon voor uitgezette widget"</string>
<string name="edit_widget" msgid="9030848101135393954">"Widget bewerken"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Verwijderen"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Widget toevoegen"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Tik om in te stellen op trillen."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Tik om geluid uit te zetten."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Ruisonderdrukking"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Tik om de beltoonmodus te wijzigen"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"geluid uit"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"geluid aanzetten"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Aan/uit-menu"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Pagina <xliff:g id="ID_1">%1$d</xliff:g> van <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Vergrendelscherm"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Je kunt deze telefoon vinden met Vind mijn apparaat, ook als die uitstaat"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Uitzetten…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Onderhoudsstappen bekijken"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Onderhoudsstappen bekijken"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Je apparaat loskoppelen"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Draai de telefoon om voor een hogere resolutie"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Opvouwbaar apparaat wordt uitgevouwen"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Opvouwbaar apparaat wordt gedraaid"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"dichtgevouwen"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"opengevouwen"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Nog <xliff:g id="PERCENTAGE">%s</xliff:g> batterijlading"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Verbind je stylus met een oplader"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Batterij van stylus bijna leeg"</string>
diff --git a/packages/SystemUI/res/values-nl/tiles_states_strings.xml b/packages/SystemUI/res/values-nl/tiles_states_strings.xml
index 60e35da..7737794 100644
--- a/packages/SystemUI/res/values-nl/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-nl/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Uit"</item>
<item msgid="578444932039713369">"Aan"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Niet beschikbaar"</item>
+ <item msgid="9061144428113385092">"Uit"</item>
+ <item msgid="2984256114867200368">"Aan"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Niet beschikbaar"</item>
<item msgid="8707481475312432575">"Uit"</item>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index b49c297..afe1e66 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"ଚାଲୁ କରନ୍ତୁ"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"ଚାଲୁ କରନ୍ତୁ"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"ନା, ଧନ୍ୟବାଦ"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"ଷ୍ଟାଣ୍ଡାର୍ଡ"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"ଏକ୍ସଟ୍ରିମ୍"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"ଅଟୋ-ରୋଟେଟ ସ୍କ୍ରିନ"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> ଆକ୍ସେସ୍ କରିବାକୁ <xliff:g id="APPLICATION">%1$s</xliff:g>କୁ ଅନୁମତି ଦେବେ?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> ଆକ୍ସେସ୍ କରିବାକୁ <xliff:g id="APPLICATION">%1$s</xliff:g>କୁ ଅନୁମତି ଦେବେ କି?\nଏହି ଆପ୍କୁ ରେକର୍ଡ କରିବାକୁ ଅନୁମତି ଦିଆଯାଇ ନାହିଁ କିନ୍ତୁ ଏହି USB ଡିଭାଇସ୍ ଜରିଆରେ ଅଡିଓ କ୍ୟାପ୍ଟର୍ କରିପାରିବ।"</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"ଆସନ୍ତାକାଲି ସ୍ୱତଃ ପୁଣି ଚାଲୁ ହେବ"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Quick Share, Find My Device ଏବଂ ଡିଭାଇସ ଲୋକେସନ ପରି ଫିଚରଗୁଡ଼ିକ ବ୍ଲୁଟୁଥ ବ୍ୟବହାର କରେ"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"ଅଧିକ ୱିଜେଟ ଯୋଗ କରନ୍ତୁ"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ୱିଜେଟଗୁଡ଼ିକୁ କଷ୍ଟମାଇଜ କରିବା ପାଇଁ ଅଧିକ ସମୟ ଦବାନ୍ତୁ"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ୱିଜେଟଗୁଡ଼ିକୁ କଷ୍ଟମାଇଜ କରନ୍ତୁ"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ଅକ୍ଷମ କରାଯାଇଥିବା ୱିଜେଟ ପାଇଁ ଆପ ଆଇକନ"</string>
<string name="edit_widget" msgid="9030848101135393954">"ୱିଜେଟକୁ ଏଡିଟ କରନ୍ତୁ"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"କାଢ଼ି ଦିଅନ୍ତୁ"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ୱିଜେଟ ଯୋଗ କରନ୍ତୁ"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s। ଭାଇବ୍ରେଟରେ ସେଟ୍ କରିବାକୁ ଟାପ୍ କରନ୍ତୁ।"</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s। ମ୍ୟୁଟ୍ କରିବାକୁ ଟାପ୍ କରନ୍ତୁ।"</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"ନଏଜ କଣ୍ଟ୍ରୋଲ"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"ରିଙ୍ଗର୍ ମୋଡ୍ ବଦଳାଇବାକୁ ଟାପ୍ କରନ୍ତୁ"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"ମ୍ୟୁଟ"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"ଅନ୍-ମ୍ୟୁଟ୍ କରନ୍ତୁ"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"ପାୱାର ମେନୁ"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"ପୃଷ୍ଠା <xliff:g id="ID_1">%1$d</xliff:g> ମୋଟ <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"ଲକ ସ୍କ୍ରିନ"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"ପାୱାର ବନ୍ଦ ଥିଲେ ମଧ୍ୟ ଆପଣ Find My Device ମାଧ୍ୟମରେ ଏହି ଫୋନକୁ ଖୋଜିପାରିବେ"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"ବନ୍ଦ କରାଯାଉଛି…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"ଯତ୍ନ ନେବା ପାଇଁ ଷ୍ଟେପଗୁଡ଼ିକ ଦେଖନ୍ତୁ"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ଯତ୍ନ ନେବା ପାଇଁ ଷ୍ଟେପଗୁଡ଼ିକ ଦେଖନ୍ତୁ"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"ଆପଣଙ୍କ ଡିଭାଇସକୁ ଅନପ୍ଲଗ କରନ୍ତୁ"</string>
@@ -1226,6 +1236,12 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ଉଚ୍ଚ ରିଜୋଲ୍ୟୁସନ ପାଇଁ ଫୋନକୁ ଫ୍ଲିପ କରନ୍ତୁ"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ଫୋଲ୍ଡ କରାଯାଇପାରୁଥିବା ଡିଭାଇସକୁ ଅନଫୋଲ୍ଡ କରାଯାଉଛି"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ଫୋଲ୍ଡ କରାଯାଇପାରୁଥିବା ଡିଭାଇସକୁ ଫ୍ଲିପ କରାଯାଉଛି"</string>
+ <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+ <skip />
+ <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+ <skip />
+ <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+ <skip />
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ବେଟେରୀ ଚାର୍ଜ ବାକି ଅଛି"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ଏକ ଚାର୍ଜର ସହ ଆପଣଙ୍କ ଷ୍ଟାଇଲସକୁ କନେକ୍ଟ କରନ୍ତୁ"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"ଷ୍ଟାଇଲସ ବେଟେରୀର ଚାର୍ଜ କମ ଅଛି"</string>
diff --git a/packages/SystemUI/res/values-or/tiles_states_strings.xml b/packages/SystemUI/res/values-or/tiles_states_strings.xml
index 43bddbf..046db2f 100644
--- a/packages/SystemUI/res/values-or/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-or/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"ବନ୍ଦ ଅଛି"</item>
<item msgid="578444932039713369">"ଚାଲୁ ଅଛି"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"ଅନୁପଲବ୍ଧ"</item>
+ <item msgid="9061144428113385092">"ବନ୍ଦ ଅଛି"</item>
+ <item msgid="2984256114867200368">"ଚାଲୁ ଅଛି"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"ଉପଲବ୍ଧ ନାହିଁ"</item>
<item msgid="8707481475312432575">"ବନ୍ଦ ଅଛି"</item>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index cd55198..8fda667 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"ਚਾਲੂ ਕਰੋ"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"ਚਾਲੂ ਕਰੋ"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"ਨਹੀਂ ਧੰਨਵਾਦ"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"ਮਿਆਰੀ"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"ਐਕਸਟ੍ਰੀਮ"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"ਸਕ੍ਰੀਨ ਸਵੈ-ਘੁਮਾਓ"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"ਕੀ <xliff:g id="USB_DEVICE">%2$s</xliff:g> ਤੱਕ <xliff:g id="APPLICATION">%1$s</xliff:g> ਨੂੰ ਪਹੁੰਚ ਕਰਨ ਦੇਣੀ ਹੈ?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"ਕੀ <xliff:g id="APPLICATION">%1$s</xliff:g> ਨੂੰ <xliff:g id="USB_DEVICE">%2$s</xliff:g> ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੇਣੀ ਹੈ?\nਇਸ ਐਪ ਨੂੰ ਰਿਕਾਰਡ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਦਿੱਤੀ ਗਈ ਪਰ ਇਹ USB ਡੀਵਾਈਸ ਰਾਹੀਂ ਆਡੀਓ ਕੈਪਚਰ ਕਰ ਸਕਦੀ ਹੈ।"</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"ਕੱਲ੍ਹ ਨੂੰ ਆਪਣੇ ਆਪ ਚਾਲੂ ਹੋ ਜਾਵੇਗਾ"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"ਕਵਿੱਕ ਸ਼ੇਅਰ, Find My Device ਅਤੇ ਡੀਵਾਈਸ ਦਾ ਟਿਕਾਣਾ ਵਰਗੀਆਂ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਬਲੂਟੁੱਥ ਦੀ ਵਰਤੋਂ ਕਰਦੀਆਂ ਹਨ"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"ਹੋਰ ਵਿਜੇਟ ਸ਼ਾਮਲ ਕਰੋ"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ਵਿਜੇਟਾਂ ਨੂੰ ਵਿਉਂਤਬੱਧ ਕਰਨ ਲਈ ਦਬਾਈ ਰੱਖੋ"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ਵਿਜੇਟ ਵਿਉਂਤਬੱਧ ਕਰੋ"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ਬੰਦ ਵਿਜੇਟ ਲਈ ਐਪ ਪ੍ਰਤੀਕ"</string>
<string name="edit_widget" msgid="9030848101135393954">"ਵਿਜੇਟ ਦਾ ਸੰਪਾਦਨ ਕਰੋ"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"ਹਟਾਓ"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ਵਿਜੇਟ ਸ਼ਾਮਲ ਕਰੋ"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s। ਥਰਥਰਾਹਟ \'ਤੇ ਸੈੱਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s। ਮਿਊਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"ਸ਼ੋਰ ਨੂੰ ਕੰਟਰੋਲ ਕਰੋ"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"ਰਿੰਗਰ ਮੋਡ ਨੂੰ ਬਦਲਣ ਲਈ ਟੈਪ ਕਰੋ"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"ਮਿਊਟ ਕਰੋ"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"ਅਣਮਿਊਟ ਕਰੋ"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"ਪਾਵਰ ਮੀਨੂ"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g> ਦਾ <xliff:g id="ID_1">%1$d</xliff:g> ਪੰਨਾ"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">" ਲਾਕ ਸਕ੍ਰੀਨ"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"ਬੰਦ ਹੋਣ \'ਤੇ ਵੀ, ਤੁਸੀਂ ਇਸ ਫ਼ੋਨ ਨੂੰ Find My Device ਦੀ ਮਦਦ ਨਾਲ ਲੱਭ ਸਕਦੇ ਹੋ"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"ਬੰਦ ਹੋ ਰਿਹਾ ਹੈ…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"ਦੇਖਭਾਲ ਦੇ ਪੜਾਅ ਦੇਖੋ"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ਦੇਖਭਾਲ ਦੇ ਪੜਾਅ ਦੇਖੋ"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"ਆਪਣਾ ਡੀਵਾਈਸ ਅਣਪਲੱਗ ਕਰੋ"</string>
@@ -1226,6 +1236,12 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ਉੱਚ ਰੈਜ਼ੋਲਿਊਸ਼ਨ ਲਈ, ਫ਼ੋਨ ਨੂੰ ਫਲਿੱਪ ਕਰੋ"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"ਮੋੜਨਯੋਗ ਡੀਵਾਈਸ ਨੂੰ ਖੋਲ੍ਹਿਆ ਜਾ ਰਿਹਾ ਹੈ"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"ਮੋੜਨਯੋਗ ਡੀਵਾਈਸ ਨੂੰ ਆਲੇ-ਦੁਆਲੇ ਫਲਿੱਪ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
+ <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+ <skip />
+ <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+ <skip />
+ <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+ <skip />
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ਬੈਟਰੀ ਬਾਕੀ"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ਆਪਣੇ ਸਟਾਈਲਸ ਨੂੰ ਚਾਰਜਰ ਨਾਲ ਕਨੈਕਟ ਕਰੋ"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"ਸਟਾਈਲਸ ਦੀ ਬੈਟਰੀ ਘੱਟ ਹੈ"</string>
diff --git a/packages/SystemUI/res/values-pa/tiles_states_strings.xml b/packages/SystemUI/res/values-pa/tiles_states_strings.xml
index 5f0ca17..df870cd 100644
--- a/packages/SystemUI/res/values-pa/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-pa/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"ਬੰਦ ਹੈ"</item>
<item msgid="578444932039713369">"ਚਾਲੂ ਹੈ"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"ਉਪਲਬਧ ਨਹੀਂ"</item>
+ <item msgid="9061144428113385092">"ਬੰਦ"</item>
+ <item msgid="2984256114867200368">"ਚਾਲੂ"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"ਅਣਉਪਲਬਧ ਹੈ"</item>
<item msgid="8707481475312432575">"ਬੰਦ ਹੈ"</item>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 76547ed..cfbfa8a 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Włącz"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Włącz"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Nie, dziękuję"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standardowe"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Intensywne"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Autoobracanie ekranu"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Zezwolić aplikacji <xliff:g id="APPLICATION">%1$s</xliff:g> na dostęp do: <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Zezwolić aplikacji <xliff:g id="APPLICATION">%1$s</xliff:g> na dostęp do urządzenia <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nTa aplikacja nie ma uprawnień do nagrywania, ale może rejestrować dźwięk za pomocą tego urządzenia USB."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatycznie włącz ponownie jutro"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funkcje takie jak Szybkie udostępnianie, Znajdź moje urządzenie i dotyczące lokalizacji urządzenia używają Bluetootha"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Dodaj więcej widżetów"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Przytrzymaj, aby dostosować widżety"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Dostosuj widżety"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikacji z wyłączonym widżetem"</string>
<string name="edit_widget" msgid="9030848101135393954">"Edytuj widżet"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Usuń"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodaj widżet"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Kliknij, by włączyć wibracje."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Kliknij, by wyciszyć."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Tłumienie szumów"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Kliknij, aby zmienić tryb dzwonka"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"wycisz"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"wyłącz wyciszenie"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menu zasilania"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Strona <xliff:g id="ID_1">%1$d</xliff:g> z <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Ekran blokady"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Możesz zlokalizować ten telefon w usłudze Znajdź moje urządzenie, nawet jeśli będzie wyłączony"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Wyłączam…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Zobacz instrukcję postępowania"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Zobacz instrukcję postępowania"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Odłącz urządzenie"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Odwróć telefon, aby uzyskać wyższą rozdzielczość"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Składane urządzenie jest rozkładane"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Składane urządzenie jest obracane"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"po zamknięciu"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"po otwarciu"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Pozostało <xliff:g id="PERCENTAGE">%s</xliff:g> baterii"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Podłącz rysik do ładowarki"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Słaba bateria w rysiku"</string>
diff --git a/packages/SystemUI/res/values-pl/tiles_states_strings.xml b/packages/SystemUI/res/values-pl/tiles_states_strings.xml
index 5e3a118..c667b99 100644
--- a/packages/SystemUI/res/values-pl/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-pl/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Wyłączony"</item>
<item msgid="578444932039713369">"Włączony"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Niedostępne"</item>
+ <item msgid="9061144428113385092">"Wyłączono"</item>
+ <item msgid="2984256114867200368">"Włączono"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Niedostępne"</item>
<item msgid="8707481475312432575">"Wyłączone"</item>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 598ef78..381c3bd 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Ativar"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Ativar"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Agora não"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Padrão"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extrema"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Giro automático da tela"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Permitir que o app <xliff:g id="APPLICATION">%1$s</xliff:g> acesse <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Permitir que <xliff:g id="APPLICATION">%1$s</xliff:g> acesse <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nEsse app não tem permissão de gravação, mas pode capturar áudio pelo dispositivo USB."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Ativar automaticamente de novo amanhã"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Recursos como o Quick Share, o Encontre Meu Dispositivo e a localização do dispositivo usam o Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Adicione mais widgets"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantenha pressionado para personalizar widgets"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ícone do app para widget desativado"</string>
<string name="edit_widget" msgid="9030848101135393954">"Editar widget"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Remover"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adicionar widget"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Toque para configurar para vibrar."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Toque para silenciar."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Controle de ruído"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Toque para mudar o modo da campainha"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"desativar o som"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"ativar o som"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menu liga/desliga"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Página <xliff:g id="ID_1">%1$d</xliff:g> de <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Tela de bloqueio"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Localize o smartphone com o Encontre Meu Dispositivo mesmo se ele estiver desligado"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Desligando…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Ver etapas de cuidado"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ver etapas de cuidado"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Desconecte seu dispositivo"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Para uma resolução maior, vire o smartphone"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo dobrável sendo aberto"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo dobrável sendo virado"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"fechado"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"aberto"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Bateria restante: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Conecte sua stylus a um carregador"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Bateria da stylus fraca"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/tiles_states_strings.xml b/packages/SystemUI/res/values-pt-rBR/tiles_states_strings.xml
index d4fd838..ae2bd05 100644
--- a/packages/SystemUI/res/values-pt-rBR/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Desativada"</item>
<item msgid="578444932039713369">"Ativada"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Indisponível"</item>
+ <item msgid="9061144428113385092">"Desativado"</item>
+ <item msgid="2984256114867200368">"Ativado"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Indisponível"</item>
<item msgid="8707481475312432575">"Desativado"</item>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 7e5a736..607a4aa 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Ativar"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Ativar"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Não"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Padrão"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extrema"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Rodar ecrã automaticamente"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Permitir que a app <xliff:g id="APPLICATION">%1$s</xliff:g> aceda ao dispositivo <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Permitir que <xliff:g id="APPLICATION">%1$s</xliff:g> aceda a <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nEsta app não recebeu autorização de gravação, mas pode capturar áudio através deste dispositivo USB."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Reativar amanhã automaticamente"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"As funcionalidades como Partilha rápida, Localizar o meu dispositivo e localização do dispositivo usam o Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -582,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Toque para ativar a vibração."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Toque para desativar o som."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Controlo de ruído"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Toque para alterar o modo de campainha"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"desativar som"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"reativar som"</string>
@@ -1223,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Para uma resolução superior, inverta o telemóvel"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo dobrável a ser desdobrado"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo dobrável a ser virado ao contrário"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"fechado"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"aberto"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> de bateria restante"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Ligue a caneta stylus a um carregador"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Bateria da caneta stylus fraca"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/tiles_states_strings.xml b/packages/SystemUI/res/values-pt-rPT/tiles_states_strings.xml
index e94b1ec..666963b 100644
--- a/packages/SystemUI/res/values-pt-rPT/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Desligado"</item>
<item msgid="578444932039713369">"Ligado"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Indisponível"</item>
+ <item msgid="9061144428113385092">"Desativado"</item>
+ <item msgid="2984256114867200368">"Ativado"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Indisponível"</item>
<item msgid="8707481475312432575">"Desligado"</item>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 598ef78..381c3bd 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Ativar"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Ativar"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Agora não"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Padrão"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extrema"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Giro automático da tela"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Permitir que o app <xliff:g id="APPLICATION">%1$s</xliff:g> acesse <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Permitir que <xliff:g id="APPLICATION">%1$s</xliff:g> acesse <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nEsse app não tem permissão de gravação, mas pode capturar áudio pelo dispositivo USB."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Ativar automaticamente de novo amanhã"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Recursos como o Quick Share, o Encontre Meu Dispositivo e a localização do dispositivo usam o Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Adicione mais widgets"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantenha pressionado para personalizar widgets"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ícone do app para widget desativado"</string>
<string name="edit_widget" msgid="9030848101135393954">"Editar widget"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Remover"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adicionar widget"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Toque para configurar para vibrar."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Toque para silenciar."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Controle de ruído"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Toque para mudar o modo da campainha"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"desativar o som"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"ativar o som"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menu liga/desliga"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Página <xliff:g id="ID_1">%1$d</xliff:g> de <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Tela de bloqueio"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Localize o smartphone com o Encontre Meu Dispositivo mesmo se ele estiver desligado"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Desligando…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Ver etapas de cuidado"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Ver etapas de cuidado"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Desconecte seu dispositivo"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Para uma resolução maior, vire o smartphone"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispositivo dobrável sendo aberto"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispositivo dobrável sendo virado"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"fechado"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"aberto"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Bateria restante: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Conecte sua stylus a um carregador"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Bateria da stylus fraca"</string>
diff --git a/packages/SystemUI/res/values-pt/tiles_states_strings.xml b/packages/SystemUI/res/values-pt/tiles_states_strings.xml
index d4fd838..ae2bd05 100644
--- a/packages/SystemUI/res/values-pt/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-pt/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Desativada"</item>
<item msgid="578444932039713369">"Ativada"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Indisponível"</item>
+ <item msgid="9061144428113385092">"Desativado"</item>
+ <item msgid="2984256114867200368">"Ativado"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Indisponível"</item>
<item msgid="8707481475312432575">"Desativado"</item>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 026e22b..857a167 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Activează"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Activează"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Nu, mulțumesc"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extremă"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Rotire automată a ecranului"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Permiți ca <xliff:g id="APPLICATION">%1$s</xliff:g> să acceseze <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Permiți accesul aplicației <xliff:g id="APPLICATION">%1$s</xliff:g> la <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nPermisiunea de înregistrare nu a fost acordată aplicației, dar aceasta poate să înregistreze conținut audio prin intermediul acestui dispozitiv USB."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Activează din nou automat mâine"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funcții precum Quick Share, Găsește-mi dispozitivul și locația dispozitivului folosesc Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Adaugă mai multe widgeturi"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Apasă lung pentru a personaliza widgeturi"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizează widgeturile"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Pictograma aplicației pentru widgetul dezactivat"</string>
<string name="edit_widget" msgid="9030848101135393954">"Editează widgetul"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Elimină"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adaugă un widget"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Atinge pentru a seta pe vibrații."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Atinge pentru a dezactiva sunetul."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Controlul zgomotului"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Atinge pentru a schimba modul soneriei"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"dezactivează sunetul"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"activează sunetul"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Meniul de pornire"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Pagina <xliff:g id="ID_1">%1$d</xliff:g> din <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Ecran de blocare"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Poți localiza telefonul folosind aplicația Găsește-mi dispozitivul chiar dacă este închis"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Se închide…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Vezi pașii pentru îngrijire"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Vezi pașii pentru îngrijire"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Deconectează dispozitivul"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Pentru o rezoluție mai mare, deschide telefonul"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Dispozitiv pliabil care este desfăcut"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Dispozitiv pliabil care este întors"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"închis"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"deschis"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> baterie rămasă"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Conectează-ți creionul la un încărcător"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Nivelul bateriei creionului este scăzut"</string>
diff --git a/packages/SystemUI/res/values-ro/tiles_states_strings.xml b/packages/SystemUI/res/values-ro/tiles_states_strings.xml
index 75565f9..ed8285d 100644
--- a/packages/SystemUI/res/values-ro/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-ro/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Dezactivată"</item>
<item msgid="578444932039713369">"Activată"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Indisponibil"</item>
+ <item msgid="9061144428113385092">"Dezactivat"</item>
+ <item msgid="2984256114867200368">"Activat"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Indisponibilă"</item>
<item msgid="8707481475312432575">"Dezactivată"</item>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 1df112b..87150cf 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Включить"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Включить"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Нет"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Стандартный режим"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Предельное"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Автоповорот экрана"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Предоставить приложению \"<xliff:g id="APPLICATION">%1$s</xliff:g>\" доступ к устройству \"<xliff:g id="USB_DEVICE">%2$s</xliff:g>\"?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Предоставить приложению \"<xliff:g id="APPLICATION">%1$s</xliff:g>\" доступ к устройству \"<xliff:g id="USB_DEVICE">%2$s</xliff:g>\"?\nПриложению не разрешено вести запись, однако с помощью этого USB-устройства оно может записывать звук."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Включить завтра автоматически"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Bluetooth используется в сервисе \"Найти устройство\", таких функциях, как Быстрая отправка, и при определении местоположения устройства"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Добавить виджеты"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Нажмите и удерживайте, чтобы настроить виджеты."</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Настроить виджеты"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Значок приложения для отключенного виджета"</string>
<string name="edit_widget" msgid="9030848101135393954">"Изменить виджет"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Удалить"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Добавить виджет"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Нажмите, чтобы включить вибрацию."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Нажмите, чтобы выключить звук."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Контроль уровня шума"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Нажмите, чтобы изменить режим звонка."</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"отключить звук"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"включить звук"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Меню кнопки питания"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Страница <xliff:g id="ID_1">%1$d</xliff:g> из <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Заблокированный экран"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"С помощью приложения \"Найти устройство\" вы можете узнать местоположение телефона, даже когда он выключен."</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Выключение…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Подробнее о действиях при перегреве…"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Подробнее о действиях при перегреве…"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Отключите устройство"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Переверните телефон и используйте основную камеру, чтобы делать снимки с более высоким разрешением."</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Складное устройство в разложенном виде"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Перевернутое складное устройство"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"устройство сложено"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"устройство разложено"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Уровень заряда батареи: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Поставьте стилус на зарядку."</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Низкий заряд батареи стилуса"</string>
diff --git a/packages/SystemUI/res/values-ru/tiles_states_strings.xml b/packages/SystemUI/res/values-ru/tiles_states_strings.xml
index 3099e00..48ecf26 100644
--- a/packages/SystemUI/res/values-ru/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-ru/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Откл."</item>
<item msgid="578444932039713369">"Вкл."</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Недоступно"</item>
+ <item msgid="9061144428113385092">"Отключено"</item>
+ <item msgid="2984256114867200368">"Включено"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Функция недоступна"</item>
<item msgid="8707481475312432575">"Откл."</item>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index f4b7d1e..e72f1ce 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"ක්රියාත්මක කරන්න"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"ක්රියාත්මක කරන්න"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"එපා ස්තුතියි"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"සම්මත"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"අතිශය"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"ස්වයංක්රීයව-භ්රමණය වන තිරය"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> හට <xliff:g id="USB_DEVICE">%2$s</xliff:g> වෙත පිවිසීමට ඉඩ දෙන්නද?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g> වෙත ප්රවේශ වීමට <xliff:g id="USB_DEVICE">%2$s</xliff:g> හට ඉඩ දෙන්නද?\n මෙම යෙදුමට පටිගත කිරීම් අවසරයක් ලබා දී නොමැති නමුත් මෙම USB උපාංගය හරහා ශ්රව්ය ග්රහණය කර ගත හැකිය."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"හෙට ස්වයංක්රීයව නැවත ක්රියාත්මක කරන්න"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"ඉක්මන් බෙදා ගැනීම, මගේ උපාංගය සෙවීම, සහ උපාංග ස්ථානය වැනි විශේෂාංග බ්ලූටූත් භාවිත කරයි"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"තවත් විජට් එක් කරන්න"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"විජට් අභිරුචිකරණය කිරීමට දිගු ඔබන්න"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"විජට්ටු අභිරුචි කරන්න"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"අබල කළ විජට් සඳහා යෙදුම් නිරූපකය"</string>
<string name="edit_widget" msgid="9030848101135393954">"විජට්ටු සංස්කරණ කරන්න"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"ඉවත් කරන්න"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"විජට්ටුව එක් කරන්න"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. කම්පනය කිරීමට සකස් කිරීමට තට්ටු කරන්න."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. නිහඬ කිරීමට තට්ටු කරන්න."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"ඝෝෂාව පාලනය"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"නාදකය වෙනස් කිරීමට තට්ටු කරන්න"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"නිහඬ කරන්න"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"නිශ්ශබ්දතාවය ඉවත් කරන්න"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"බල මෙනුව"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g> න් <xliff:g id="ID_1">%1$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"අගුලු තිරය"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"බලය ක්රියාවිරහිත වූ විට පවා ඔබට මගේ උපාංගය සෙවීම මගින් මෙම දුරකථනය සොයාගත හැක"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"වසා දමමින්…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"රැකවරණ පියවර බලන්න"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"රැකවරණ පියවර බලන්න"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"ඔබේ උපාංගය ගලවන්න"</string>
@@ -1226,6 +1236,12 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"ඉහළ විභේදනය සඳහා, දුරකථනය හරවන්න"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"දිග හැරෙමින් පවතින නැමිය හැකි උපාංගය"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"වටා පෙරළෙමින් තිබෙන නැමිය හැකි උපාංගය"</string>
+ <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+ <skip />
+ <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+ <skip />
+ <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+ <skip />
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> බැටරිය ඉතිරිව ඇත"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"ඔබේ පන්හිඳ චාජරයකට සම්බන්ධ කරන්න"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"පන්හිඳ බැටරිය අඩුයි"</string>
diff --git a/packages/SystemUI/res/values-si/tiles_states_strings.xml b/packages/SystemUI/res/values-si/tiles_states_strings.xml
index 48e8cc4..cbbc0e7 100644
--- a/packages/SystemUI/res/values-si/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-si/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"අක්රියයි"</item>
<item msgid="578444932039713369">"සක්රියයි"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"නොමැත"</item>
+ <item msgid="9061144428113385092">"ක්රියාවිරහිතයි"</item>
+ <item msgid="2984256114867200368">"ක්රියාත්මකයි"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"නොමැත"</item>
<item msgid="8707481475312432575">"අක්රියයි"</item>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 0f579da..6614c0d 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Zapnúť"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Zapnúť"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Nie, vďaka"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Štandardný"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Super"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Automatické otočenie obrazovky"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Povoliť aplikácii <xliff:g id="APPLICATION">%1$s</xliff:g> prístup k zariadeniu <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Povoliť aplikácii <xliff:g id="APPLICATION">%1$s</xliff:g> pristupovať k zariadeniu <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nTejto aplikácii nebolo udelené povolenie na nahrávanie, môže však snímať zvuk cez toto zariadenie USB."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automaticky zajtra znova zapnúť"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funkcie, ako sú Quick Share, Nájdi moje zariadenie a poloha zariadenia, používajú Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Pridať ďalšie miniaplikácie"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Miniaplikácie prispôsobíte dlhým stlačením"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Prispôsobiť miniaplikácie"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona deaktivovanej miniaplikácie"</string>
<string name="edit_widget" msgid="9030848101135393954">"Upraviť miniaplikáciu"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Odstrániť"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Pridať miniaplikáciu"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Klepnutím nastavíte vibrovanie."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Klepnutím vypnete zvuk."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Ovládanie šumu"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Režim zvonenia zmeníte klepnutím"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"vypnite zvuk"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"zapnite zvuk"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Ponuka vypínača"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Strana <xliff:g id="ID_1">%1$d</xliff:g> z <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Uzamknutá obrazovka"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Pomocou funkcie Nájdi moje zariadenie môžete zistiť polohu tohto telefónu, aj keď je vypnutý"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Vypína sa…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Zobraziť opatrenia"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Zobraziť opatrenia"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Odpojte zariadenie"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Ak chcete vyššie rozlíšenie, prevráťte telefón"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Rozloženie skladacieho zariadenia"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Prevrátenie skladacieho zariadenia"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"zložené"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"rozložené"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Zostáva <xliff:g id="PERCENTAGE">%s</xliff:g> batérie"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Pripojte dotykové pero k nabíjačke"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Stav batérie dotykového pera je nízky"</string>
diff --git a/packages/SystemUI/res/values-sk/tiles_states_strings.xml b/packages/SystemUI/res/values-sk/tiles_states_strings.xml
index fdfcd27db..50f5c25 100644
--- a/packages/SystemUI/res/values-sk/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-sk/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Vypnuté"</item>
<item msgid="578444932039713369">"Zapnuté"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Nedostupné"</item>
+ <item msgid="9061144428113385092">"Vypnuté"</item>
+ <item msgid="2984256114867200368">"Zapnuté"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Nie je k dispozícii"</item>
<item msgid="8707481475312432575">"Vypnuté"</item>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 7acaa54..4a46742 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Vklopi"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Vklopi"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Ne, hvala"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standardno"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Strogo"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Samodejno zasukaj zaslon"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Ali aplikaciji <xliff:g id="APPLICATION">%1$s</xliff:g> dovolite dostop do dodatka USB <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Ali aplikaciji <xliff:g id="APPLICATION">%1$s</xliff:g> dovolite dostop do naprave <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nTa aplikacija sicer nima dovoljenja za snemanje, vendar bi lahko zajemala zvok prek te naprave USB."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Samodejno znova vklopi jutri"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funkcije, kot sta Hitro deljenje in Poišči mojo napravo, ter lokacija naprave, uporabljajo Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Dodajte več pripomočkov"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Pridržite za prilagajanje pripomočkov"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Prilagajanje pripomočkov"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikacije za onemogočen pripomoček"</string>
<string name="edit_widget" msgid="9030848101135393954">"Urejanje pripomočka"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Odstrani"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodajanje pripomočka"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Dotaknite se, če želite nastaviti vibriranje."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Dotaknite se, če želite izklopiti zvok."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Omejevanje hrupa"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Dotaknite se, če želite spremeniti način zvonjenja."</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"izklop zvoka"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"vklop zvoka"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Meni za vklop/izklop"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_1">%1$d</xliff:g>. stran od <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Zaklenjen zaslon"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"S storitvijo Poišči mojo napravo lahko ta telefon poiščete, tudi če je izklopljen"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Zaustavljanje …"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Oglejte si navodila za ukrepanje"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Oglejte si navodila za ukrepanje"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Odklopite napravo"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Za višjo ločljivost obrnite telefon"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Razpiranje zložljive naprave"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Obračanje zložljive naprave"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"zaprto"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"razprto"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Preostanek energije baterije: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Povežite pisalo s polnilnikom."</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Skoraj prazna baterija pisala"</string>
diff --git a/packages/SystemUI/res/values-sl/tiles_states_strings.xml b/packages/SystemUI/res/values-sl/tiles_states_strings.xml
index 3804d63..33ba216 100644
--- a/packages/SystemUI/res/values-sl/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-sl/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Izklopljeno"</item>
<item msgid="578444932039713369">"Vklopljeno"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Ni na voljo"</item>
+ <item msgid="9061144428113385092">"Izklopljeno"</item>
+ <item msgid="2984256114867200368">"Vklopljeno"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Ni na voljo"</item>
<item msgid="8707481475312432575">"Izklopljeno"</item>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 21a974f..1fb6251 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Aktivizo"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Aktivizo"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Jo, faleminderit"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Në kushte ekstreme"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Rrotullimi automatik i ekranit"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Të lejohet <xliff:g id="APPLICATION">%1$s</xliff:g> të ketë qasje te <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Dëshiron të lejosh që <xliff:g id="APPLICATION">%1$s</xliff:g> të ketë qasje te <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nKëtij aplikacioni nuk i është dhënë leje për regjistrim, por mund të regjistrojë audio përmes kësaj pajisjeje USB."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Aktivizoje automatikisht nesër"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Veçoritë si \"Ndarja e shpejtë\", \"Gjej pajisjen time\" dhe vendndodhja e pajisjes përdorin Bluetooth-in"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Shto miniaplikacione të tjera"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Shtyp gjatë për të personalizuar miniaplikacionet"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizo miniaplikacionet"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona e aplikacionit për miniaplikacionin e çaktivizuar"</string>
<string name="edit_widget" msgid="9030848101135393954">"Modifiko miniaplikacionin"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Hiq"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Shto miniaplikacionin"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Trokit për ta vendosur në dridhje."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Trokit për ta çaktivizuar."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Kontrolli i zhurmës"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Trokit për të ndryshuar modalitetin e ziles"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"çaktivizo audion"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"aktivizo audion"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menyja e energjisë"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Faqja <xliff:g id="ID_1">%1$d</xliff:g> nga <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Ekrani i kyçjes"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Mund ta gjesh këtë telefon me \"Gjej pajisjen time\" edhe kur është i fikur"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Po fiket…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Shiko hapat për kujdesin"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Shiko hapat për kujdesin"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Shkëpute pajisjen"</string>
@@ -1226,6 +1236,12 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Për rezolucion më të lartë, përmbys telefonin"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Pajisja e palosshme duke u hapur"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Pajisja e palosshme duke u rrotulluar"</string>
+ <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+ <skip />
+ <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+ <skip />
+ <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+ <skip />
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Përqindja e mbetur e baterisë: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Lidhe stilolapsin me një karikues"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Bateria e stilolapsit në nivel të ulët"</string>
diff --git a/packages/SystemUI/res/values-sq/tiles_states_strings.xml b/packages/SystemUI/res/values-sq/tiles_states_strings.xml
index 6318700..fa06795 100644
--- a/packages/SystemUI/res/values-sq/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-sq/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Joaktive"</item>
<item msgid="578444932039713369">"Aktiv"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Nuk ofrohet"</item>
+ <item msgid="9061144428113385092">"Joaktiv"</item>
+ <item msgid="2984256114867200368">"Aktiv"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Nuk ofrohet"</item>
<item msgid="8707481475312432575">"Joaktive"</item>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 2f52f90..3444c80 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Укључи"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Укључи"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Не, хвала"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Стандардно"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Екстремно"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Аутоматско ротирање екрана"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Дозвољавате да <xliff:g id="APPLICATION">%1$s</xliff:g> приступа уређају <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Желите ли да дозволите да <xliff:g id="APPLICATION">%1$s</xliff:g> приступа уређају <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nОва апликација нема дозволу за снимање, али би могла да снима звук помоћу овог USB уређаја."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Аутоматски поново укључи сутра"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Функције као што су Quick Share, Пронађи мој уређај и локација уређаја користе Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Додајте још виџета"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Дуги притисак за прилагођавање виџета"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Прилагоди виџете"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Икона апликације за онемогућен виџет"</string>
<string name="edit_widget" msgid="9030848101135393954">"Измени виџет"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Уклони"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Додај виџет"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Додирните да бисте подесили на вибрацију."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Додирните да бисте искључили звук."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Контрола шума"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Додирните да бисте променили режим звона"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"искључите звук"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"укључите звук"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Мени дугмета за укључивање"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_1">%1$d</xliff:g>. страна од <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Закључан екран"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Можете да лоцирате овај телефон помоћу услуге Пронађи мој уређај чак и када је искључен"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Искључује се…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Погледајте упозорења"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Погледајте упозорења"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Искључите уређај"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"За већу резолуцију обрните телефон"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Уређај на преклоп се отвара"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Уређај на преклоп се обрће"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"затворено"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"отворено"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Преостало је још<xliff:g id="PERCENTAGE">%s</xliff:g> батерије"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Повежите писаљку са пуњачем"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Низак ниво батерије писаљке"</string>
diff --git a/packages/SystemUI/res/values-sr/tiles_states_strings.xml b/packages/SystemUI/res/values-sr/tiles_states_strings.xml
index e4cf0b6..55f5a3f 100644
--- a/packages/SystemUI/res/values-sr/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-sr/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Искључено"</item>
<item msgid="578444932039713369">"Укључено"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Недоступно"</item>
+ <item msgid="9061144428113385092">"Искључено"</item>
+ <item msgid="2984256114867200368">"Укључено"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Недоступно"</item>
<item msgid="8707481475312432575">"Искључено"</item>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 20bc7e7..2ca3d5d 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Aktivera"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Aktivera"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Nej tack"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Effektivare läget"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Rotera skärmen automatiskt"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Vill du ge <xliff:g id="APPLICATION">%1$s</xliff:g> åtkomst till <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Vill du ge <xliff:g id="APPLICATION">%1$s</xliff:g> åtkomst till <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nAppen har inte fått inspelningsbehörighet men kan spela in ljud via denna USB-enhet."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Aktivera automatiskt igen i morgon"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Funktioner som Snabbdelning, Hitta min enhet och enhetens plats använder Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Lägg till fler widgetar"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Tryck länge för att anpassa widgetar"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Anpassa widgetar"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Appikon för inaktiverad widget"</string>
<string name="edit_widget" msgid="9030848101135393954">"Redigera widget"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Ta bort"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Lägg till widget"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Tryck här om du vill aktivera vibrationsläget."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Tryck här om du vill stänga av ljudet."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Bruskontroll"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Tryck för att ändra ringsignalens läge"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"stänga av ljudet"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"slå på ljudet"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Startmeny"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Sida <xliff:g id="ID_1">%1$d</xliff:g> av <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Låsskärm"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Du kan hitta den här telefonen med Hitta min enhet även när den är avstängd"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Avslutar …"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Visa alla skötselråd"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Visa alla skötselråd"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Koppla ur enheten"</string>
@@ -1226,6 +1236,12 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Vänd telefonen för högre upplösning"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"En vikbar enhet viks upp"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"En vikbar enhet vänds"</string>
+ <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+ <skip />
+ <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+ <skip />
+ <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+ <skip />
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> av batteriet återstår"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Anslut e-pennan till en laddare"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"E-pennans batterinivå är låg"</string>
diff --git a/packages/SystemUI/res/values-sv/tiles_states_strings.xml b/packages/SystemUI/res/values-sv/tiles_states_strings.xml
index 8981ac7..f921f27 100644
--- a/packages/SystemUI/res/values-sv/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-sv/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Av"</item>
<item msgid="578444932039713369">"På"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Inte tillgängligt"</item>
+ <item msgid="9061144428113385092">"Av"</item>
+ <item msgid="2984256114867200368">"På"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Inte tillgängligt"</item>
<item msgid="8707481475312432575">"Av"</item>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index fa0e7b5..b48a0f5 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Washa"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Washa"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Hapana"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Kawaida"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Kwa kina"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Skrini ijizungushe kiotomatiki"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Ungependa kuruhusu <xliff:g id="APPLICATION">%1$s</xliff:g> ifikie <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Ungependa kuruhusu <xliff:g id="APPLICATION">%1$s</xliff:g> ifikie <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nProgramu hii haijapewa ruhusa ya kurekodi lakini inaweza kurekodi sauti kupitia kifaa hiki cha USB."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Iwashe tena kesho kiotomatiki"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Vipengele kama vile Kutuma Haraka, Tafuta Kifaa Changu na mahali kifaa kilipo hutumia Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Weka wijeti zingine"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Bonyeza kwa muda mrefu uweke mapendeleo ya wijeti"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Badilisha wijeti upendavyo"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Aikoni ya programu ya wijeti iliyozimwa"</string>
<string name="edit_widget" msgid="9030848101135393954">"Badilisha wijeti"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Ondoa"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ongeza wijeti"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Gusa ili uweke mtetemo."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Gusa ili usitishe."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Kidhibiti cha Kelele"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Gusa ili ubadilishe hali ya programu inayotoa milio ya simu"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"zima sauti"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"washa sauti"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Menyu ya kuzima/kuwasha"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Ukurasa wa <xliff:g id="ID_1">%1$d</xliff:g> kati ya <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Skrini iliyofungwa"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Unaweza kutambua mahali ilipo simu hii ukitumia programu ya Tafuta Kifaa Changu hata kama simu hiyo imezimwa"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Inazima…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Angalia hatua za utunzaji"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Angalia hatua za utunzaji"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Chomoa kifaa chako"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Kwa ubora wa juu, geuza simu"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Kifaa kinachokunjwa kikikunjuliwa"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Kifaa kinachokunjwa kikigeuzwa"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"kimekunjwa"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"kimefunguliwa"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Chaji ya betri imesalia <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Unganisha stylus yako kwenye chaja"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Chaji ya betri ya Stylus imepungua"</string>
diff --git a/packages/SystemUI/res/values-sw/tiles_states_strings.xml b/packages/SystemUI/res/values-sw/tiles_states_strings.xml
index 08a1f14..227eee8 100644
--- a/packages/SystemUI/res/values-sw/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-sw/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Kimezimwa"</item>
<item msgid="578444932039713369">"Kimewashwa"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Haipatikani"</item>
+ <item msgid="9061144428113385092">"Imezimwa"</item>
+ <item msgid="2984256114867200368">"Imewashwa"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Hakipatikani"</item>
<item msgid="8707481475312432575">"Kimezimwa"</item>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 0f360e6..b954064 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"இயக்கு"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"இயக்கு"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"வேண்டாம்"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"நிலையானது"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"மேம்பட்டது"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"திரையைத் தானாகச் சுழற்று"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="USB_DEVICE">%2$s</xliff:g>ஐ அணுக, <xliff:g id="APPLICATION">%1$s</xliff:g> ஆப்ஸை அனுமதிக்கவா?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="USB_DEVICE">%2$s</xliff:g>ஐப் பயன்படுத்த <xliff:g id="APPLICATION">%1$s</xliff:g>ஐ அனுமதிக்கவா?\nஇந்த ஆப்ஸிற்கு ரெக்கார்டு செய்வதற்கான அனுமதி வழங்கப்படவில்லை, எனினும் இந்த USB சாதனம் மூலம் ஆடியோவைப் பதிவுசெய்யும்."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"நாளைக்குத் தானாகவே மீண்டும் இயக்கப்படும்"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"விரைவுப் பகிர்தல், Find My Device போன்ற அம்சங்களும் சாதன இருப்பிடமும் புளூடூத்தைப் பயன்படுத்துகின்றன"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"கூடுதல் விட்ஜெட்களைச் சேருங்கள்"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"விட்ஜெட்களைப் பிரத்தியேகமாக்க நீண்ட நேரம் அழுத்துக"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"விட்ஜெட்களைப் பிரத்தியேகமாக்குங்கள்"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"முடக்கப்பட்ட விட்ஜெட்டுக்கான ஆப்ஸ் ஐகான்"</string>
<string name="edit_widget" msgid="9030848101135393954">"விட்ஜெட்டைத் திருத்து"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"அகற்றும்"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"விட்ஜெட்டைச் சேர்"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. அதிர்விற்கு அமைக்க, தட்டவும்."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. ஒலியடக்க, தட்டவும்."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"இரைச்சல் கட்டுப்பாடு"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"ரிங்கர் பயன்முறையை மாற்ற தட்டவும்"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"ஒலியடக்கும்"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"ஒலி இயக்கும்"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"பவர் மெனு"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"பக்கம் <xliff:g id="ID_1">%1$d</xliff:g> / <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"லாக் ஸ்கிரீன்"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"மொபைல் பவர் ஆஃப் செய்யப்பட்டிருக்கும்போதும் Find My Device மூலம் அதன் இருப்பிடத்தைக் கண்டறியலாம்"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"ஷட் டவுன் ஆகிறது…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"மேலும் விவரங்களுக்கு இதைப் பார்க்கவும்"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"மேலும் விவரங்களுக்கு இதைப் பார்க்கவும்"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"சாதன இணைப்பைத் துண்டித்தல்"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"உயர் தெளிவுத்திறனுக்கு, மொபைலை ஃபிளிப் செய்யுங்கள்"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"மடக்கத்தக்க சாதனம் திறக்கப்படுகிறது"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"மடக்கத்தக்க சாதனம் ஃபிளிப் செய்யப்பட்டு திருப்பப்படுகிறது"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"மடக்கப்பட்டது"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"விரிக்கப்பட்டது"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> பேட்டரி மீதமுள்ளது"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"உங்கள் ஸ்டைலஸைச் சார்ஜருடன் இணையுங்கள்"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"ஸ்டைலஸின் பேட்டரி குறைவாக உள்ளது"</string>
diff --git a/packages/SystemUI/res/values-ta/tiles_states_strings.xml b/packages/SystemUI/res/values-ta/tiles_states_strings.xml
index 741d6de..6043cf2 100644
--- a/packages/SystemUI/res/values-ta/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-ta/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"முடக்கப்பட்டுள்ளது"</item>
<item msgid="578444932039713369">"இயக்கப்பட்டுள்ளது"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"இல்லை"</item>
+ <item msgid="9061144428113385092">"முடக்கப்பட்டுள்ளது"</item>
+ <item msgid="2984256114867200368">"இயக்கப்பட்டுள்ளது"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"கிடைக்கவில்லை"</item>
<item msgid="8707481475312432575">"முடக்கப்பட்டுள்ளது"</item>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 4e8e2d0..75f872f 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"ఆన్ చేయి"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"ఆన్ చేయండి"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"వద్దు, ధన్యవాదాలు"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"స్టాండర్డ్"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"ఎక్స్ట్రీమ్"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"స్క్రీన్ ఆటో-రొటేట్"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="USB_DEVICE">%2$s</xliff:g>ని యాక్సెస్ చేయడానికి <xliff:g id="APPLICATION">%1$s</xliff:g>ని అనుమతించాలా?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="USB_DEVICE">%2$s</xliff:g>యాక్సెస్ చేయడానికి <xliff:g id="APPLICATION">%1$s</xliff:g>ను అనుమతించాలా?\nఈ యాప్నకు రికార్డ్ చేసే అనుమతి మంజూరు చేయబడలేదు, కానీ ఈ USB పరికరం ద్వారా ఆడియోను క్యాప్చర్ చేయగలదు."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"రేపు మళ్లీ ఆటోమేటిక్గా ఆన్ చేస్తుంది"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"క్విక్ షేర్, Find My Device, పరికర లొకేషన్ వంటి ఫీచర్లు బ్లూటూత్ను ఉపయోగిస్తాయి"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"మరిన్ని విడ్జెట్లను జోడించండి"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"విడ్జెట్లను అనుకూలీకరించడానికి, నొక్కి, ఉంచండి"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"విడ్జెట్లను అనుకూలంగా మార్చండి"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"డిజేబుల్ చేయబడిన విడ్జెట్ కోసం యాప్ చిహ్నం"</string>
<string name="edit_widget" msgid="9030848101135393954">"విడ్జెట్ను ఎడిట్ చేయండి"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"తీసివేయండి"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"విడ్జెట్ను జోడించండి"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. వైబ్రేట్ అయ్యేలా సెట్ చేయడం కోసం నొక్కండి."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. మ్యూట్ చేయడానికి నొక్కండి."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"నాయిస్ కంట్రోల్"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"రింగర్ మోడ్ను మార్చడానికి ట్యాప్ చేయండి"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"మ్యూట్ చేయి"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"అన్మ్యూట్ చేయి"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"పవర్ మెనూ"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_2">%2$d</xliff:g>లో <xliff:g id="ID_1">%1$d</xliff:g>వ పేజీ"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"లాక్ స్క్రీన్"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"పవర్ ఆఫ్లో ఉన్నప్పుడు కూడా మీరు Find My Deviceతో ఈ ఫోన్ను గుర్తించవచ్చు"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"షట్ డౌన్ చేయబడుతోంది…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"తీసుకోవాల్సిన జాగ్రత్తలు ఏమిటో చూడండి"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"తీసుకోవాల్సిన జాగ్రత్తలు ఏమిటో చూడండి"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"మీ పరికరాన్ని అన్ప్లగ్ చేయండి"</string>
@@ -1226,6 +1236,12 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"అధిక రిజల్యూషన్ కోసం, ఫోన్ను తిప్పండి"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"మడవగల పరికరం విప్పబడుతోంది"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"మడవగల పరికరం చుట్టూ తిప్పబడుతోంది"</string>
+ <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+ <skip />
+ <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+ <skip />
+ <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+ <skip />
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> బ్యాటరీ మిగిలి ఉంది"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"మీ స్టైలస్ను ఛార్జర్కి కనెక్ట్ చేయండి"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"తక్కువ స్టైలస్ బ్యాటరీ"</string>
diff --git a/packages/SystemUI/res/values-te/tiles_states_strings.xml b/packages/SystemUI/res/values-te/tiles_states_strings.xml
index 6ff2934..370aeb0 100644
--- a/packages/SystemUI/res/values-te/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-te/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"ఆఫ్లో ఉంది"</item>
<item msgid="578444932039713369">"ఆన్లో ఉంది"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"అందుబాటులో లేదు"</item>
+ <item msgid="9061144428113385092">"ఆఫ్లో ఉంది"</item>
+ <item msgid="2984256114867200368">"ఆన్లో ఉంది"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"అందుబాటులో లేదు"</item>
<item msgid="8707481475312432575">"ఆఫ్లో ఉంది"</item>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 98075c5..94243b03 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"เปิด"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"เปิด"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"ไม่เป็นไร"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"มาตรฐาน"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"สูงสุด"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"หมุนหน้าจออัตโนมัติ"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"อนุญาตให้ <xliff:g id="APPLICATION">%1$s</xliff:g> เข้าถึง <xliff:g id="USB_DEVICE">%2$s</xliff:g> ไหม"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"อนุญาตให้ <xliff:g id="APPLICATION">%1$s</xliff:g> เข้าถึง <xliff:g id="USB_DEVICE">%2$s</xliff:g> ไหม\nแอปนี้ไม่ได้รับอนุญาตให้อัดเสียงแต่อาจเก็บเสียงผ่านอุปกรณ์ USB นี้ได้"</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"เปิดอีกครั้งโดยอัตโนมัติในวันพรุ่งนี้"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"ฟีเจอร์ต่างๆ เช่น Quick Share, หาอุปกรณ์ของฉัน และตำแหน่งของอุปกรณ์ ใช้บลูทูธ"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"เพิ่มวิดเจ็ตอีก"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"กดค้างเพื่อปรับแต่งวิดเจ็ต"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ปรับแต่งวิดเจ็ต"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ไอคอนแอปสำหรับวิดเจ็ตที่ปิดใช้อยู่"</string>
<string name="edit_widget" msgid="9030848101135393954">"แก้ไขวิดเจ็ต"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"นำออก"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"เพิ่มวิดเจ็ต"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s แตะเพื่อตั้งค่าให้สั่น"</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s แตะเพื่อปิดเสียง"</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"การควบคุมเสียง"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"แตะเพื่อเปลี่ยนโหมดเสียงเรียกเข้า"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"ปิดเสียง"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"เปิดเสียง"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"เมนูเปิด/ปิด"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"หน้า <xliff:g id="ID_1">%1$d</xliff:g> จาก <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"หน้าจอล็อก"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"คุณจะหาตำแหน่งของโทรศัพท์นี้ได้ด้วยแอปหาอุปกรณ์ของฉันแม้จะปิดเครื่องอยู่ก็ตาม"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"กำลังปิด…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"ดูขั้นตอนในการดูแลรักษา"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"ดูขั้นตอนในการดูแลรักษา"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"ถอดปลั๊กอุปกรณ์"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"พลิกด้านโทรศัพท์เพื่อให้ได้ภาพที่มีความละเอียดมากขึ้น"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"อุปกรณ์ที่พับได้กำลังกางออก"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"อุปกรณ์ที่พับได้กำลังพลิกไปมา"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"พับ"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"กางออก"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"เหลือแบตเตอรี่ <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"เชื่อมต่อสไตลัสกับที่ชาร์จ"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"แบตเตอรี่สไตลัสเหลือน้อย"</string>
diff --git a/packages/SystemUI/res/values-th/tiles_states_strings.xml b/packages/SystemUI/res/values-th/tiles_states_strings.xml
index d961385..acaf9f0 100644
--- a/packages/SystemUI/res/values-th/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-th/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"ปิด"</item>
<item msgid="578444932039713369">"เปิด"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"ไม่พร้อมใช้งาน"</item>
+ <item msgid="9061144428113385092">"ปิด"</item>
+ <item msgid="2984256114867200368">"เปิด"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"ไม่พร้อมใช้งาน"</item>
<item msgid="8707481475312432575">"ปิด"</item>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index ff8d84f..99b61e9 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"I-on"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"I-on"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Huwag na lang"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standard"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Extreme"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"I-auto rotate ang screen"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Payagan ang <xliff:g id="APPLICATION">%1$s</xliff:g> na ma-access ang <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Payagan ang <xliff:g id="APPLICATION">%1$s</xliff:g> na i-access ang <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nHindi nabigyan ng pahintulot ang app na ito para mag-record pero nakakapag-capture ito ng audio sa pamamagitan ng USB device na ito."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Awtomatikong i-on ulit bukas"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Guamgamit ng Bluetooth ang mga feature tulad ng Quick Share, Hanapin ang Aking Device, at lokasyon ng device"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Magdagdag ng higit pang widget"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Pindutin nang matagal para i-customize ang mga widget"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"I-customize ang mga widget"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icon ng app para sa na-disable na widget"</string>
<string name="edit_widget" msgid="9030848101135393954">"I-edit ang widget"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Alisin"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Magdagdag ng widget"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. I-tap upang itakda na mag-vibrate."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. I-tap upang i-mute."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Pagkontrol sa Ingay"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"I-tap para baguhin ang ringer mode"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"i-mute"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"i-unmute"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Power menu"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Page <xliff:g id="ID_1">%1$d</xliff:g> ng <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Lock screen"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Puwede mong hanapin ang teleponong ito gamit ang Hanapin ang Aking Device kahit kapag naka-off ito"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Nagsa-shut down…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Tingnan ang mga hakbang sa pangangalaga"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Tingnan ang mga hakbang sa pangangalaga"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Bunutin sa saksakan ang device"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Para sa mas mataas na resolution, i-flip ang telepono"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Ina-unfold na foldable na device"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Fini-flip na foldable na device"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"naka-fold"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"hindi naka-fold"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> baterya na lang ang natitira"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Ikonekta sa charger ang iyong stylus"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Paubos na ang baterya ng stylus"</string>
diff --git a/packages/SystemUI/res/values-tl/tiles_states_strings.xml b/packages/SystemUI/res/values-tl/tiles_states_strings.xml
index a12c010..6de62df 100644
--- a/packages/SystemUI/res/values-tl/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-tl/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Naka-off"</item>
<item msgid="578444932039713369">"Naka-on"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Hindi available"</item>
+ <item msgid="9061144428113385092">"Naka-off"</item>
+ <item msgid="2984256114867200368">"Naka-on"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Hindi available"</item>
<item msgid="8707481475312432575">"Naka-off"</item>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 2cdc6e7..c20c62a 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Aç"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Aç"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Hayır, teşekkürler"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standart"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Yüksek"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Ekranı otomatik döndür"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> uygulamasının <xliff:g id="USB_DEVICE">%2$s</xliff:g> cihazına erişmesine izin verilsin mi?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g> uygulamasının <xliff:g id="USB_DEVICE">%2$s</xliff:g> cihazına erişmesine izin verilsin mi?\nBu uygulamaya kayıt izni verilmemiş ancak bu USB cihazı aracılığıyla sesleri yakalayabilir."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Yarın otomatik olarak tekrar aç"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Quick Share, Cihazımı Bul ve cihaz konumu gibi özellikler Bluetooth\'u kullanır"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Daha fazla widget ekle"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Widget\'ları özelleştirmek için uzun basın"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Widget\'ları özelleştir"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Devre dışı bırakılan widget\'ın uygulama simgesi"</string>
<string name="edit_widget" msgid="9030848101135393954">"Widget\'ı düzenle"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Kaldır"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Widget ekle"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Titreşime ayarlamak için dokunun."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Sesi kapatmak için dokunun."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Gürültü Kontrolü"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Telefon zili modunu değiştirmek için dokunun"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"sesi kapat"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"sesi aç"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Güç menüsü"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Sayfa <xliff:g id="ID_1">%1$d</xliff:g> / <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Kilit ekranı"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Bu telefonu kapalıyken bile Cihazımı Bul işleviyle bulabilirsiniz."</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Kapanıyor…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Bakımla ilgili adımlara bakın"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Bakımla ilgili adımlara bakın"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Cihazınızın fişini çekin"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Daha yüksek çözünürlük için telefonu çevirin"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Katlanabilir cihaz açılıyor"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Katlanabilir cihaz döndürülüyor"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"katlanmış"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"katlanmamış"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> pil kaldı"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Ekran kaleminizi bir şarj cihazına bağlayın"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Ekran kaleminin pil seviyesi düşük"</string>
diff --git a/packages/SystemUI/res/values-tr/tiles_states_strings.xml b/packages/SystemUI/res/values-tr/tiles_states_strings.xml
index c6a8aec..0c086f8 100644
--- a/packages/SystemUI/res/values-tr/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-tr/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Kapalı"</item>
<item msgid="578444932039713369">"Açık"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Yok"</item>
+ <item msgid="9061144428113385092">"Kapalı"</item>
+ <item msgid="2984256114867200368">"Açık"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Kullanılamıyor"</item>
<item msgid="8707481475312432575">"Kapalı"</item>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index c89ae75..3338bce 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Увімкнути"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Увімкнути"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Ні, дякую"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Стандартний режим"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Екстремальний режим"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Автообертання екрана"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Надати додатку <xliff:g id="APPLICATION">%1$s</xliff:g> доступ до такого аксесуара: <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Надати додатку <xliff:g id="APPLICATION">%1$s</xliff:g> доступ до <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nЦей додаток не має дозволу на записування звуку, але може фіксувати його через цей USB-пристрій."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Автоматично ввімкнути знову завтра"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Такі функції, як швидкий обмін, \"Знайти пристрій\" і визначення місцезнаходження пристрою, використовують Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Додати більше віджетів"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Утримуйте, щоб налаштувати віджети"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Налаштувати віджети"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Значок додатка для вимкненого віджета"</string>
<string name="edit_widget" msgid="9030848101135393954">"Редагувати віджет"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Видалити"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Додати віджет"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Торкніться, щоб налаштувати вібросигнал."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Торкніться, щоб вимкнути звук."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Контроль шуму"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Торкніться, щоб змінити режим дзвінка"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"вимкнути звук"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"увімкнути звук"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Меню кнопки живлення"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Сторінка <xliff:g id="ID_1">%1$d</xliff:g> з <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Заблокований екран"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Ви зможете визначити місцеположення цього телефона, навіть коли його вимкнено, за допомогою сервісу Знайти пристрій"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Вимкнення…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Переглянути запобіжні заходи"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Переглянути запобіжні заходи"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Від’єднайте пристрій"</string>
@@ -1226,6 +1236,12 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Для вищої роздільної здатності переверніть телефон"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Розкладний пристрій у розкладеному стані"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Розкладний пристрій обертається"</string>
+ <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+ <skip />
+ <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+ <skip />
+ <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+ <skip />
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Заряд акумулятора: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Підключіть стилус до зарядного пристрою"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Низький заряд акумулятора стилуса"</string>
diff --git a/packages/SystemUI/res/values-uk/tiles_states_strings.xml b/packages/SystemUI/res/values-uk/tiles_states_strings.xml
index a8e1ab8..fd3fb08 100644
--- a/packages/SystemUI/res/values-uk/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-uk/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Вимкнено"</item>
<item msgid="578444932039713369">"Увімкнено"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Недоступно"</item>
+ <item msgid="9061144428113385092">"Вимкнено"</item>
+ <item msgid="2984256114867200368">"Увімкнено"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Недоступно"</item>
<item msgid="8707481475312432575">"Вимкнено"</item>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 3237f32..bbc7767 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"آن کریں"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"آن کریں"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"نہیں شکریہ"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"معیاری"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"انتہائی"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"سکرین کو خودکار طور پر گھمائیں"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> کو <xliff:g id="USB_DEVICE">%2$s</xliff:g> تک رسائی حاصل کرنے کی اجازت دیں؟"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g> کو <xliff:g id="USB_DEVICE">%2$s</xliff:g> تک رسائی دیں؟\nاس ایپ کو ریکارڈ کی اجازت عطا نہیں کی گئی ہے مگر اس USB آلہ سے کیپچر کر سکتے ہیں۔"</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"کل دوبارہ خودکار طور پر آن ہوگا"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"فوری اشتراک، میرا آلہ ڈھونڈیں، اور آلہ کے مقام جیسی خصوصیات بلوٹوتھ کا استعمال کرتی ہیں"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -582,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s۔ ارتعاش پر سیٹ کرنے کیلئے تھپتھپائیں۔"</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s۔ خاموش کرنے کیلئے تھپتھپائیں۔"</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"شور کنٹرول"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"رنگر وضع تبدیل کرنے کیلئے تھپتھپائیں"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"خاموش کریں"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"غیر خاموش کریں"</string>
@@ -1223,6 +1236,12 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"زیادہ ریزولوشن کے لیے، فون پلٹائیں"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"فولڈ ہونے والے آلے کو کھولا جا رہا ہے"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"فولڈ ہونے والے آلے کو گھمایا جا رہا ہے"</string>
+ <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+ <skip />
+ <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+ <skip />
+ <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+ <skip />
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> بیٹری باقی ہے"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"اپنے اسٹائلس کو چارجر منسلک کریں"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"اسٹائلس بیٹری کم ہے"</string>
diff --git a/packages/SystemUI/res/values-ur/tiles_states_strings.xml b/packages/SystemUI/res/values-ur/tiles_states_strings.xml
index 6d1e707..4957e59 100644
--- a/packages/SystemUI/res/values-ur/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-ur/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"آف ہے"</item>
<item msgid="578444932039713369">"آن ہے"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"دستیاب نہیں ہے"</item>
+ <item msgid="9061144428113385092">"آف ہے"</item>
+ <item msgid="2984256114867200368">"آن ہے"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"دستیاب نہیں ہے"</item>
<item msgid="8707481475312432575">"آف ہے"</item>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 478fcdb..4967115 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Yoqish"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Yoqish"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Kerak emas"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Standart"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Qattiq tejash"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Ekranning avtomatik burilishi"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="APPLICATION">%1$s</xliff:g> ilovasiga <xliff:g id="USB_DEVICE">%2$s</xliff:g> qurilmasidan foydalanishga ruxsat berilsinmi?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="APPLICATION">%1$s</xliff:g> ilovasiga <xliff:g id="USB_DEVICE">%2$s</xliff:g> qurilmasidan foydalanish uchun ruxsat berilsinmi?\nBu ilovaga yozib olish ruxsati berilmagan, lekin shu USB orqali ovozlarni yozib olishi mumkin."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Ertaga yana avtomatik yoqilsin"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Tezkor ulashuv, Qurilmamni top va qurilma geolokatsiyasi kabi funksiyalar Bluetooth ishlatadi"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Koʻproq vidjetlar qoʻshish"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Vidjetlarni sozlash uchun bosib turing"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Vidjetlarni moslashtirish"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Faolsizlantirilgan vidjet uchun ilova belgisi"</string>
<string name="edit_widget" msgid="9030848101135393954">"Vidjetni tahrirlash"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Olib tashlash"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Vidjet kiritish"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Tebranishni yoqish uchun ustiga bosing."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Ovozsiz qilish uchun ustiga bosing."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Shovqin boshqaruvi"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Jiringlagich rejimini oʻzgartirish uchun bosing"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"ovozsiz qilish"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"ovozni yoqish"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Quvvat menyusi"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"<xliff:g id="ID_1">%1$d</xliff:g>-sahifa, jami: <xliff:g id="ID_2">%2$d</xliff:g> ta sahifa"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Ekran qulfi"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Oʻchiq boʻlsa ham “Qurilmani top” funksiyasi yordamida bu telefonni topish mumkin"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Oʻchirilmoqda…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Batafsil axborot"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Batafsil axborot"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Qurilmani uzing"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Yuqori aniqlik uchun telefonni aylantiring"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Buklanadigan qurilma ochilmoqda"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Buklanadigan qurilma aylantirilmoqda"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"buklangan"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"buklanmagan"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Batareya quvvati: <xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Stilusni quvvat manbaiga ulang"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Stilus batareyasi kam"</string>
diff --git a/packages/SystemUI/res/values-uz/tiles_states_strings.xml b/packages/SystemUI/res/values-uz/tiles_states_strings.xml
index 0558c4a..670c56c 100644
--- a/packages/SystemUI/res/values-uz/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-uz/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Oʻchiq"</item>
<item msgid="578444932039713369">"Yoniq"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Mavjud emas"</item>
+ <item msgid="9061144428113385092">"Oʻchiq"</item>
+ <item msgid="2984256114867200368">"Yoniq"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Ishlamaydi"</item>
<item msgid="8707481475312432575">"Oʻchiq"</item>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 7e8638b..0b411b5 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Bật"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Bật"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Không, cảm ơn"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Tiêu chuẩn"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Siêu tiết kiệm"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Tự động xoay màn hình"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Cho phép <xliff:g id="APPLICATION">%1$s</xliff:g> truy cập <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Cho phép <xliff:g id="APPLICATION">%1$s</xliff:g> truy cập vào <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nỨng dụng này chưa được cấp quyền ghi âm nhưng vẫn có thể ghi âm thông qua thiết bị USB này."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Tự động bật lại vào ngày mai"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Các tính năng như Chia sẻ nhanh, Tìm thiết bị của tôi và dịch vụ vị trí trên thiết bị có sử dụng Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Thêm tiện ích khác"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Nhấn và giữ để tuỳ chỉnh tiện ích"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Tuỳ chỉnh tiện ích"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Biểu tượng ứng dụng của tiện ích đã bị vô hiệu hoá"</string>
<string name="edit_widget" msgid="9030848101135393954">"Chỉnh sửa tiện ích"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Xoá"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Thêm tiện ích"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Nhấn để đặt chế độ rung."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Nhấn để tắt tiếng."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Kiểm soát tiếng ồn"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Nhấn để thay đổi chế độ chuông"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"tắt tiếng"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"bật tiếng"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Trình đơn nguồn"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Trang <xliff:g id="ID_1">%1$d</xliff:g> / <xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Màn hình khóa"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Bạn có thể định vị chiếc điện thoại này bằng ứng dụng Tìm thiết bị của tôi ngay cả khi điện thoại tắt nguồn"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Đang tắt…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Xem các bước chăm sóc"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Xem các bước chăm sóc"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Rút thiết bị ra"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Để có độ phân giải cao hơn, hãy lật điện thoại"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Thiết bị có thể gập lại đang được mở ra"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Thiết bị có thể gập lại đang được lật ngược"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"gập"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"mở"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"Còn <xliff:g id="PERCENTAGE">%s</xliff:g> pin"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Hãy kết nối bút cảm ứng với bộ sạc"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Bút cảm ứng bị yếu pin"</string>
diff --git a/packages/SystemUI/res/values-vi/tiles_states_strings.xml b/packages/SystemUI/res/values-vi/tiles_states_strings.xml
index eee10d3..4df2d91 100644
--- a/packages/SystemUI/res/values-vi/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-vi/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Đang tắt"</item>
<item msgid="578444932039713369">"Đang bật"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Không có"</item>
+ <item msgid="9061144428113385092">"Đang tắt"</item>
+ <item msgid="2984256114867200368">"Đang bật"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Không hoạt động"</item>
<item msgid="8707481475312432575">"Đang tắt"</item>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 2552138..9a75884 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"开启"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"开启"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"不用了"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"标准"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"超级"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"自动旋转屏幕"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"要允许<xliff:g id="APPLICATION">%1$s</xliff:g>访问<xliff:g id="USB_DEVICE">%2$s</xliff:g>吗?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"是否允许<xliff:g id="APPLICATION">%1$s</xliff:g>访问<xliff:g id="USB_DEVICE">%2$s</xliff:g>?\n此应用未获得录音权限,但能通过此 USB 设备录制音频。"</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"明天自动重新开启"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"快速分享、查找我的设备、设备位置信息等功能会使用蓝牙"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"添加更多微件"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"长按即可自定义微件"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"自定义微件"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"已停用微件的应用图标"</string>
<string name="edit_widget" msgid="9030848101135393954">"修改微件"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"移除"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"添加微件"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s。点按即可设为振动。"</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s。点按即可设为静音。"</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"噪声控制"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"点按即可更改振铃器模式"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"静音"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"取消静音"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"电源菜单"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"第 <xliff:g id="ID_1">%1$d</xliff:g> 页,共 <xliff:g id="ID_2">%2$d</xliff:g> 页"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"锁定屏幕"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"即使手机已关机,您也可以通过“查找我的设备”找到这部手机"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"正在关机…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"查看处理步骤"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"查看处理步骤"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"拔出设备"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"若要获得更高的分辨率,请翻转手机"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"正在展开可折叠设备"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"正在翻转可折叠设备"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"折叠状态"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"展开状态"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s/%2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"电池还剩 <xliff:g id="PERCENTAGE">%s</xliff:g> 的电量"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"请将触控笔连接充电器"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"触控笔电池电量低"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml b/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml
index 82ab671..08a1551 100644
--- a/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"已关闭"</item>
<item msgid="578444932039713369">"已开启"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"不可用"</item>
+ <item msgid="9061144428113385092">"已停用"</item>
+ <item msgid="2984256114867200368">"已启用"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"不可用"</item>
<item msgid="8707481475312432575">"已关闭"</item>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 5941dad..2cf6da4 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"開啟"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"開啟"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"不用了,謝謝"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"標準"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"超級"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"自動旋轉螢幕"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"要允許「<xliff:g id="APPLICATION">%1$s</xliff:g>」存取「<xliff:g id="USB_DEVICE">%2$s</xliff:g>」嗎?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"要允許「<xliff:g id="APPLICATION">%1$s</xliff:g>」存取「<xliff:g id="USB_DEVICE">%2$s</xliff:g>」嗎?\n此應用程式尚未獲授予錄音權限,但可透過此 USB 裝置記錄音訊。"</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"明天自動重新開啟"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"「快速共享」、「尋找我的裝置」和裝置位置等功能都會使用藍牙"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"新增更多小工具"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"長按即可自訂小工具"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"自訂小工具"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"已停用小工具的應用程式圖示"</string>
<string name="edit_widget" msgid="9030848101135393954">"編輯小工具"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"移除"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"新增小工具"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s。輕按即可設為震動。"</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s。輕按即可設為靜音。"</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"噪音控制"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"輕按即可變更響鈴模式"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"靜音"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"取消靜音"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"電源選單"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"第 <xliff:g id="ID_1">%1$d</xliff:g> 頁 (共 <xliff:g id="ID_2">%2$d</xliff:g> 頁)"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"螢幕鎖定"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"即使手機關機,仍可透過「尋找我的裝置」尋找此手機"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"正在關機…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"查看保養步驟"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"查看保養步驟"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"拔除裝置"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"如要提高解像度,請切換至手機後置鏡頭"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"正在展開折疊式裝置"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"正在翻轉折疊式裝置"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"已摺疊"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"已打開"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"剩餘電量:<xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"將觸控筆連接充電器"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"觸控筆電量不足"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/tiles_states_strings.xml b/packages/SystemUI/res/values-zh-rHK/tiles_states_strings.xml
index 6bac275..e29d230 100644
--- a/packages/SystemUI/res/values-zh-rHK/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"已關閉"</item>
<item msgid="578444932039713369">"已開啟"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"未有提供"</item>
+ <item msgid="9061144428113385092">"關閉"</item>
+ <item msgid="2984256114867200368">"開啟"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"無法使用"</item>
<item msgid="8707481475312432575">"已關閉"</item>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index c46d831..12ca94c 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"開啟"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"開啟"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"不用了,謝謝"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"標準"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"超級"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"自動旋轉螢幕"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"要允許「<xliff:g id="APPLICATION">%1$s</xliff:g>」存取「<xliff:g id="USB_DEVICE">%2$s</xliff:g>」嗎?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"要允許「<xliff:g id="APPLICATION">%1$s</xliff:g>」存取「<xliff:g id="USB_DEVICE">%2$s</xliff:g>」嗎?\n這個應用程式未取得錄製權限,但可以透過這部 USB 裝置錄製音訊。"</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"明天自動重新開啟"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"快速分享、尋找我的裝置和裝置位置資訊等功能都會使用藍牙"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"新增更多小工具"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"長按即可自訂小工具"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"自訂小工具"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"所停用小工具的應用程式圖示"</string>
<string name="edit_widget" msgid="9030848101135393954">"編輯小工具"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"移除"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"新增小工具"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s。輕觸即可設為震動。"</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s。輕觸即可設為靜音。"</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"噪音控制"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"輕觸即可變更鈴聲模式"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"靜音"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"取消靜音"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"電源鍵選單"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"第 <xliff:g id="ID_1">%1$d</xliff:g> 頁,共 <xliff:g id="ID_2">%2$d</xliff:g> 頁"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"鎖定畫面"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"即使這支手機關機,仍可透過「尋找我的裝置」找出手機位置"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"關機中…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"查看處理步驟"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"查看處理步驟"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"拔除裝置"</string>
@@ -1226,6 +1236,12 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"如要提高解析度,請切換至手機後置鏡頭"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"正在展開的折疊式裝置"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"正在翻轉折疊式裝置"</string>
+ <!-- no translation found for quick_settings_rotation_posture_folded (2430280856312528289) -->
+ <skip />
+ <!-- no translation found for quick_settings_rotation_posture_unfolded (6372316273574167114) -->
+ <skip />
+ <!-- no translation found for rotation_tile_with_posture_secondary_label_template (7648496484163318886) -->
+ <skip />
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"剩餘電量:<xliff:g id="PERCENTAGE">%s</xliff:g>"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"將觸控筆接上充電器"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"觸控筆電力不足"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/tiles_states_strings.xml b/packages/SystemUI/res/values-zh-rTW/tiles_states_strings.xml
index 5794bf1..85e1796 100644
--- a/packages/SystemUI/res/values-zh-rTW/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"已關閉"</item>
<item msgid="578444932039713369">"已開啟"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"無法使用"</item>
+ <item msgid="9061144428113385092">"已關閉"</item>
+ <item msgid="2984256114867200368">"已開啟"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"無法使用"</item>
<item msgid="8707481475312432575">"已關閉"</item>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 0bbac05..b6f4a19 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -31,6 +31,8 @@
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Vula"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Vula"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Cha ngiyabonga"</string>
+ <string name="standard_battery_saver_text" msgid="6855876746552374119">"Okujwayelekile"</string>
+ <string name="extreme_battery_saver_text" msgid="8455810156739865335">"Kakhulu"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Ukuzulazula kweskrini okuzenzakalelayo"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Vumela i-<xliff:g id="APPLICATION">%1$s</xliff:g> ukufinyelela i-<xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Vumela i-<xliff:g id="APPLICATION">%1$s</xliff:g> ukuthi ifinyelele ku-<xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nLolu hlelo lokusebenza alunikeziwe imvume yokurekhoda kodwa lingathatha umsindo ngale divayisi ye-USB."</string>
@@ -269,7 +271,10 @@
<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="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Vula ngokuzenzekela futhi kusasa"</string>
- <string name="turn_on_bluetooth_auto_info" msgid="8831410009251539988">"Izakhi ezifana Nokwabelana Ngokushesha, okuthi Thola Idivayisi Yami, kanye nendawo yedivayisi zisebenzisa i-Bluetooth"</string>
+ <!-- no translation found for turn_on_bluetooth_auto_info_disabled (8267380591344023327) -->
+ <skip />
+ <!-- no translation found for turn_on_bluetooth_auto_info_enabled (4802071533678400330) -->
+ <skip />
<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>
@@ -429,8 +434,7 @@
<string name="cta_label_to_open_widget_picker" msgid="3874946756976360699">"Engeza amawijethi engeziwe"</string>
<string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Cindezela isikhathi eside ukuze wenze ngokwezifiso amawijethi"</string>
<string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Yenza ngokwezifiso amawijethi"</string>
- <!-- no translation found for icon_description_for_disabled_widget (4693151565003206943) -->
- <skip />
+ <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Isithonjana se-app sewijethi evaliwe"</string>
<string name="edit_widget" msgid="9030848101135393954">"Hlela amawijethi"</string>
<string name="button_to_remove_widget" msgid="3948204829181214098">"Susa"</string>
<string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Engeza iwijethi"</string>
@@ -583,6 +587,14 @@
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Thepha ukuze usethele ekudlidlizeni."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Thepha ukuze uthulise."</string>
<string name="volume_panel_noise_control_title" msgid="7413949943872304474">"Ulawulo Lomsindo"</string>
+ <!-- no translation found for volume_panel_spatial_audio_title (3367048857932040660) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_off (4177490084606772989) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_fixed (3136080137827746046) -->
+ <skip />
+ <!-- no translation found for volume_panel_spatial_audio_tracking (5711115234001762974) -->
+ <skip />
<string name="volume_ringer_change" msgid="3574969197796055532">"Thepha ukuze ushintshe imodi yokukhala"</string>
<string name="volume_ringer_hint_mute" msgid="4263821214125126614">"thulisa"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"susa ukuthula"</string>
@@ -837,10 +849,8 @@
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Imenyu yamandla"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"Ikhasi <xliff:g id="ID_1">%1$d</xliff:g> kwangu-<xliff:g id="ID_2">%2$d</xliff:g>"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"Khiya isikrini"</string>
- <!-- no translation found for finder_active (7907846989716941952) -->
- <skip />
- <!-- no translation found for shutdown_progress (5464239146561542178) -->
- <skip />
+ <string name="finder_active" msgid="7907846989716941952">"Ungabeka le foni ngokuthi Thola Ifoni Yami ngisho noma ivaliwe"</string>
+ <string name="shutdown_progress" msgid="5464239146561542178">"Iyacisha…"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"Bona izinyathelo zokunakekelwa"</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"Bona izinyathelo zokunakekelwa"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Khipha idivayisi yakho"</string>
@@ -1226,6 +1236,9 @@
<string name="rear_display_unfolded_bottom_sheet_description" msgid="7229961336309960201">"Ukuze uthole ukulungiswa okuphezulu, phendula ifoni"</string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Idivayisi egoqekayo iyembulwa"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Idivayisi egoqekayo iphendulwa nxazonke"</string>
+ <string name="quick_settings_rotation_posture_folded" msgid="2430280856312528289">"kugoqiwe"</string>
+ <string name="quick_settings_rotation_posture_unfolded" msgid="6372316273574167114">"kuvuliwe"</string>
+ <string name="rotation_tile_with_posture_secondary_label_template" msgid="7648496484163318886">"%1$s / %2$s"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> ibhethri elisele"</string>
<string name="stylus_battery_low_subtitle" msgid="3583843128908823273">"Xhuma i-stylus yakho kushaja"</string>
<string name="stylus_battery_low" msgid="7134370101603167096">"Ibhethri le-stylus liphansi"</string>
diff --git a/packages/SystemUI/res/values-zu/tiles_states_strings.xml b/packages/SystemUI/res/values-zu/tiles_states_strings.xml
index 8c7b652..5c5a67c 100644
--- a/packages/SystemUI/res/values-zu/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-zu/tiles_states_strings.xml
@@ -126,9 +126,11 @@
<item msgid="8259411607272330225">"Valiwe"</item>
<item msgid="578444932039713369">"Vuliwe"</item>
</string-array>
- <!-- no translation found for tile_states_record_issue:0 (1727196795383575383) -->
- <!-- no translation found for tile_states_record_issue:1 (9061144428113385092) -->
- <!-- no translation found for tile_states_record_issue:2 (2984256114867200368) -->
+ <string-array name="tile_states_record_issue">
+ <item msgid="1727196795383575383">"Ayitholakali"</item>
+ <item msgid="9061144428113385092">"Kuvaliwe"</item>
+ <item msgid="2984256114867200368">"Kuvuliwe"</item>
+ </string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"Akutholakali"</item>
<item msgid="8707481475312432575">"Valiwe"</item>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 71ae0d7..035cfdc 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -223,6 +223,7 @@
<item type="id" name="lock_icon" />
<item type="id" name="lock_icon_bg" />
<item type="id" name="burn_in_layer" />
+ <item type="id" name="burn_in_layer_empty_view" />
<item type="id" name="communal_tutorial_indicator" />
<item type="id" name="nssl_placeholder_barrier_bottom" />
<item type="id" name="ambient_indication_container" />
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 5dbdd18..b8f20f6 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -680,8 +680,10 @@
<string name="accessibility_quick_settings_bluetooth_device_tap_to_activate">activate</string>
<!-- QuickSettings: Bluetooth auto on tomorrow [CHAR LIMIT=NONE]-->
<string name="turn_on_bluetooth_auto_tomorrow">Automatically turn on again tomorrow</string>
- <!-- QuickSettings: Bluetooth auto on info text [CHAR LIMIT=NONE]-->
- <string name="turn_on_bluetooth_auto_info">Features like Quick Share, Find My Device, and device location use Bluetooth</string>
+ <!-- QuickSettings: Bluetooth auto on info text when disabled [CHAR LIMIT=NONE]-->
+ <string name="turn_on_bluetooth_auto_info_disabled">Features like Quick Share, Find My Device, and device location use Bluetooth</string>
+ <!-- QuickSettings: Bluetooth auto on info text when enabled [CHAR LIMIT=NONE]-->
+ <string name="turn_on_bluetooth_auto_info_enabled">Bluetooth will turn on tomorrow at 5 AM</string>
<!-- QuickSettings: Bluetooth secondary label for the battery level of a connected device [CHAR LIMIT=20]-->
<string name="quick_settings_bluetooth_secondary_label_battery_level"><xliff:g id="battery_level_as_percentage">%s</xliff:g> battery</string>
@@ -1133,6 +1135,14 @@
<string name="hub_mode_add_widget_button_text">Add widget</string>
<!-- Text for the button that exits the hub mode editing mode. [CHAR LIMIT=50] -->
<string name="hub_mode_editing_exit_button_text">Done</string>
+ <!-- Title for the dialog that redirects users to change allowed widget category in settings. [CHAR LIMIT=NONE] -->
+ <string name="dialog_title_to_allow_any_widget">Allow any widget on lock screen?</string>
+ <!-- Text for the button in the dialog that opens when tapping on disabled widgets. [CHAR LIMIT=NONE] -->
+ <string name="button_text_to_open_settings">Open settings</string>
+ <!-- Title of a dialog. This text is confirming that the user wants to turn on access to their work apps, which the user had previously paused. "Work" is an adjective. [CHAR LIMIT=30] -->
+ <string name="work_mode_off_title">Unpause work apps?</string>
+ <!-- Title for button to unpause on work profile. [CHAR LIMIT=NONE] -->
+ <string name="work_mode_turn_on">Unpause</string>
<!-- Related to user switcher --><skip/>
@@ -1526,8 +1536,12 @@
<!-- Media device casting volume slider label [CHAR_LIMIT=20] -->
<string name="media_device_cast">Cast</string>
- <!-- A message shown when the notification volume changing is disabled because of the muted ring stream [CHAR_LIMIT=40]-->
+ <!-- A message shown when the notification volume changing is disabled because of the muted ring stream [CHAR_LIMIT=50]-->
<string name="stream_notification_unavailable">Unavailable because ring is muted</string>
+ <!-- A message shown when the alarm volume changing is disabled because of the don't disturb mode [CHAR_LIMIT=50]-->
+ <string name="stream_alarm_unavailable">Unavailable because Do Not Disturb is on</string>
+ <!-- A message shown when the media volume changing is disabled because of the don't disturb mode [CHAR_LIMIT=50]-->
+ <string name="stream_media_unavailable">Unavailable because Do Not Disturb is on</string>
<!-- Shown in the header of quick settings to indicate to the user that their phone ringer is on vibrate. [CHAR_LIMIT=NONE] -->
<!-- Shown in the header of quick settings to indicate to the user that their phone ringer is on silent (muted). [CHAR_LIMIT=NONE] -->
@@ -1538,8 +1552,16 @@
<string name="volume_stream_content_description_vibrate_a11y">%1$s. Tap to set to vibrate.</string>
<string name="volume_stream_content_description_mute_a11y">%1$s. Tap to mute.</string>
- <!-- Label for button to enabled/disable live caption [CHAR_LIMIT=30] -->
+ <!-- Label for button to enabled/disable active noise cancellation [CHAR_LIMIT=30] -->
<string name="volume_panel_noise_control_title">Noise Control</string>
+ <!-- Label for button to enabled/disable spatial audio [CHAR_LIMIT=30] -->
+ <string name="volume_panel_spatial_audio_title">Spatial Audio</string>
+ <!-- Label for button to disable spatial audio [CHAR_LIMIT=20] -->
+ <string name="volume_panel_spatial_audio_off">Off</string>
+ <!-- Label for button to enabled spatial audio [CHAR_LIMIT=20] -->
+ <string name="volume_panel_spatial_audio_fixed">Fixed</string>
+ <!-- Label for button to enabled head tracking [CHAR_LIMIT=20] -->
+ <string name="volume_panel_spatial_audio_tracking">Head Tracking</string>
<string name="volume_ringer_change">Tap to change ringer mode</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index ce08ca3..59516be 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -965,6 +965,10 @@
<item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
</style>
+ <style name="Widget.SliceView.VolumePanel">
+ <item name="hideHeaderRow">true</item>
+ </style>
+
<style name="Theme.VolumePanelActivity.Popup" parent="@style/Theme.SystemUI.Dialog">
<item name="android:dialogCornerRadius">44dp</item>
<item name="android:colorBackground">?androidprv:attr/materialColorSurfaceContainerHigh
@@ -987,6 +991,11 @@
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowLightStatusBar">true</item>
+
+ <!--
+ TODO(b/309578419): Make activities handle insets properly and then remove this.
+ -->
+ <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
<style name="Theme.EditWidgetsActivity"
diff --git a/packages/SystemUI/res/xml/fileprovider.xml b/packages/SystemUI/res/xml/fileprovider.xml
index b67378e..71cc05d 100644
--- a/packages/SystemUI/res/xml/fileprovider.xml
+++ b/packages/SystemUI/res/xml/fileprovider.xml
@@ -19,4 +19,5 @@
<cache-path name="leak" path="leak/"/>
<external-path name="screenrecord" path="."/>
<cache-path name="multi_user" path="multi_user/" />
-</paths>
\ No newline at end of file
+ <root-path name="traces" path="/data/local/traces"/>
+</paths>
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 6e611fe..42ba05c 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -35,6 +35,9 @@
srcs: [
":statslog-SystemUI-java-gen",
],
+ libs: [
+ "androidx.annotation_annotation",
+ ],
}
android_library {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 84c8ea7..26e91b6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -122,7 +122,7 @@
case PROMPT_REASON_USER_REQUEST:
return R.string.kg_prompt_after_user_lockdown_password;
case PROMPT_REASON_PREPARE_FOR_UPDATE:
- return R.string.kg_prompt_reason_timeout_password;
+ return R.string.kg_prompt_added_security_password;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
return R.string.kg_prompt_reason_timeout_password;
case PROMPT_REASON_TRUSTAGENT_EXPIRED:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index bf8900d..caa74780 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -323,7 +323,7 @@
resId = R.string.kg_prompt_after_user_lockdown_pattern;
break;
case PROMPT_REASON_PREPARE_FOR_UPDATE:
- resId = R.string.kg_prompt_reason_timeout_pattern;
+ resId = R.string.kg_prompt_added_security_pattern;
break;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
resId = R.string.kg_prompt_reason_timeout_pattern;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index bcab6f0..fbe9edf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -134,7 +134,7 @@
case PROMPT_REASON_USER_REQUEST:
return R.string.kg_prompt_after_user_lockdown_pin;
case PROMPT_REASON_PREPARE_FOR_UPDATE:
- return R.string.kg_prompt_reason_timeout_pin;
+ return R.string.kg_prompt_added_security_pin;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
return R.string.kg_prompt_reason_timeout_pin;
case PROMPT_REASON_TRUSTAGENT_EXPIRED:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 9421f15..c0ae4a1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -53,9 +53,6 @@
import com.android.systemui.animation.ViewHierarchyAnimator;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.shared.model.TransitionState;
-import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.plugins.clocks.ClockController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.power.shared.model.ScreenPowerState;
@@ -104,7 +101,6 @@
private final Rect mClipBounds = new Rect();
private final KeyguardInteractor mKeyguardInteractor;
private final PowerInteractor mPowerInteractor;
- private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
private final DozeParameters mDozeParameters;
private View mStatusArea = null;
@@ -112,7 +108,6 @@
private Boolean mSplitShadeEnabled = false;
private Boolean mStatusViewCentered = true;
- private boolean mGoneToAodTransitionRunning = false;
private DumpManager mDumpManager;
private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener =
@@ -181,7 +176,6 @@
KeyguardLogger logger,
InteractionJankMonitor interactionJankMonitor,
KeyguardInteractor keyguardInteractor,
- KeyguardTransitionInteractor keyguardTransitionInteractor,
DumpManager dumpManager,
PowerInteractor powerInteractor) {
super(keyguardStatusView);
@@ -197,7 +191,6 @@
mDumpManager = dumpManager;
mKeyguardInteractor = keyguardInteractor;
mPowerInteractor = powerInteractor;
- mKeyguardTransitionInteractor = keyguardTransitionInteractor;
}
@Override
@@ -232,6 +225,7 @@
mDumpManager.registerDumpable(getInstanceName(), this);
if (migrateClocksToBlueprint()) {
startCoroutines(EmptyCoroutineContext.INSTANCE);
+ mView.setVisibility(View.GONE);
}
}
@@ -247,15 +241,6 @@
dozeTimeTick();
}
}, context);
-
- collectFlow(mView, mKeyguardTransitionInteractor.getGoneToAodTransition(),
- (TransitionStep step) -> {
- if (step.getTransitionState() == TransitionState.RUNNING) {
- mGoneToAodTransitionRunning = true;
- } else {
- mGoneToAodTransitionRunning = false;
- }
- }, context);
}
public KeyguardStatusView getView() {
@@ -326,7 +311,7 @@
* Set keyguard status view alpha.
*/
public void setAlpha(float alpha) {
- if (!mKeyguardVisibilityHelper.isVisibilityAnimating() && !mGoneToAodTransitionRunning) {
+ if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
mView.setAlpha(alpha);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index 2000028..f5a6cb3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -88,6 +88,10 @@
boolean keyguardFadingAway,
boolean goingToFullShade,
int oldStatusBarState) {
+ if (migrateClocksToBlueprint()) {
+ log("Ignoring KeyguardVisibilityelper, migrateClocksToBlueprint flag on");
+ return;
+ }
Assert.isMainThread();
PropertyAnimator.cancelAnimation(mView, AnimatableProperty.ALPHA);
boolean isOccluded = mKeyguardStateController.isOccluded();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
index 0ef3d20..a90d4b2 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
@@ -118,6 +118,12 @@
iconResId = R.drawable.pip_ic_close_white
)
)
+
+ // Ensure this is unfocusable & uninteractable
+ isClickable = false
+ isFocusable = false
+ importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
+
// END DragToInteractView modification
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index d3e85e0..1f04599 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -113,13 +113,8 @@
/* Moves position without updating underlying percentage position. Can be animated. */
void moveToPosition(PointF position, boolean animateMovement) {
- if (Flags.floatingMenuImeDisplacementAnimation()) {
- moveToPositionX(position.x, animateMovement);
- moveToPositionY(position.y, animateMovement);
- } else {
- moveToPositionX(position.x, /* animateMovement = */ false);
- moveToPositionY(position.y, /* animateMovement = */ false);
- }
+ moveToPositionX(position.x, animateMovement);
+ moveToPositionY(position.y, animateMovement);
}
void moveToPositionX(float positionX) {
@@ -127,7 +122,7 @@
}
void moveToPositionX(float positionX, boolean animateMovement) {
- if (animateMovement && Flags.floatingMenuImeDisplacementAnimation()) {
+ if (animateMovement) {
springMenuWith(DynamicAnimation.TRANSLATION_X,
createSpringForce(),
/* velocity = */ 0,
@@ -142,7 +137,7 @@
}
void moveToPositionY(float positionY, boolean animateMovement) {
- if (animateMovement && Flags.floatingMenuImeDisplacementAnimation()) {
+ if (animateMovement) {
springMenuWith(DynamicAnimation.TRANSLATION_Y,
createSpringForce(),
/* velocity = */ 0,
@@ -455,7 +450,7 @@
? MIN_PERCENT
: Math.min(MAX_PERCENT, position.y / draggableBounds.height());
- if (Flags.floatingMenuImeDisplacementAnimation() && !writeToPosition) {
+ if (!writeToPosition) {
mMenuView.onEdgeChangedIfNeeded();
} else {
mMenuView.persistPositionAndUpdateEdge(new Position(percentageX, percentageY));
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
index e57323b..35fe6b1 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
@@ -74,6 +74,12 @@
addView(mTextView, Index.TEXT_VIEW,
new LayoutParams(/* width= */ 0, WRAP_CONTENT, /* weight= */ 1));
addView(mUndoButton, Index.UNDO_BUTTON, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+
+ // The message box is not focusable, but will announce its contents when it appears.
+ // The textView and button are still interactable.
+ setClickable(false);
+ setFocusable(false);
+ setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
index 6299739..270fedc 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -22,6 +22,8 @@
import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -99,6 +101,10 @@
loadLayoutResources();
addView(mTargetFeaturesView);
+
+ setClickable(false);
+ setFocusable(false);
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
}
@Override
@@ -222,8 +228,7 @@
}
// We can skip animating if FAB is not visible
- if (Flags.floatingMenuImeDisplacementAnimation()
- && animateMovement && getVisibility() == VISIBLE) {
+ if (animateMovement && getVisibility() == VISIBLE) {
mMenuAnimationController.moveToPosition(position, /* animateMovement = */ true);
// onArrivalAtPosition() is called at the end of the animation.
} else {
@@ -329,7 +334,7 @@
mMoveToTuckedListener.onMoveToTuckedChanged(isMoveToTucked);
}
- if (Flags.floatingMenuOverlapsNavBarsFlag() && !Flags.floatingMenuAnimatedTuck()) {
+ if (!Flags.floatingMenuAnimatedTuck()) {
if (isMoveToTucked) {
final float halfWidth = getMenuWidth() / 2.0f;
final boolean isOnLeftSide = mMenuAnimationController.isOnLeftSide();
@@ -435,7 +440,14 @@
}
mMenuAnimationController.flingMenuThenSpringToEdge(
getMenuPosition().x, 100f, 0f);
- mContext.startActivity(getIntentForEditScreen());
+
+ Intent intent = getIntentForEditScreen();
+ PackageManager packageManager = getContext().getPackageManager();
+ List<ResolveInfo> activities = packageManager.queryIntentActivities(intent,
+ PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY));
+ if (!activities.isEmpty()) {
+ mContext.startActivity(intent);
+ }
}
void incrementTexMetricForAllTargets(String metric) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
index 4865fce..760e1c3 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
@@ -34,7 +34,6 @@
import androidx.annotation.DimenRes;
-import com.android.systemui.Flags;
import com.android.systemui.res.R;
import java.lang.annotation.Retention;
@@ -155,11 +154,6 @@
final int margin = getMenuMargin();
final Rect draggableBounds = new Rect(getWindowAvailableBounds());
- if (!Flags.floatingMenuOverlapsNavBarsFlag()) {
- // Initializes start position for mapping the translation of the menu view.
- draggableBounds.offsetTo(/* newLeft= */ 0, /* newTop= */ 0);
- }
-
draggableBounds.top += margin;
draggableBounds.right -= getMenuWidth();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 6d4baf4..97e38f4 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -17,6 +17,7 @@
package com.android.systemui.accessibility.floatingmenu;
import static android.view.WindowInsets.Type.ime;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
import static androidx.core.view.WindowInsetsCompat.Type;
@@ -48,6 +49,7 @@
import android.os.Looper;
import android.os.UserHandle;
import android.provider.Settings;
+import android.util.ArraySet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
@@ -64,6 +66,7 @@
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
+import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto;
@@ -162,35 +165,45 @@
final Runnable mDismissMenuAction = new Runnable() {
@Override
public void run() {
- mSecureSettings.putStringForUser(
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* value= */ "",
- UserHandle.USER_CURRENT);
+ if (android.view.accessibility.Flags.a11yQsShortcut()) {
+ mAccessibilityManager.enableShortcutsForTargets(
+ /* enable= */ false,
+ ShortcutConstants.UserShortcutType.SOFTWARE,
+ new ArraySet<>(mAccessibilityManager.getAccessibilityShortcutTargets(
+ ACCESSIBILITY_BUTTON)),
+ mSecureSettings.getRealUserHandle(UserHandle.USER_CURRENT)
+ );
+ } else {
+ mSecureSettings.putStringForUser(
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* value= */ "",
+ UserHandle.USER_CURRENT);
- final List<ComponentName> hardwareKeyShortcutComponents =
- mAccessibilityManager.getAccessibilityShortcutTargets(
- ACCESSIBILITY_SHORTCUT_KEY)
- .stream()
- .map(ComponentName::unflattenFromString)
- .toList();
+ final List<ComponentName> hardwareKeyShortcutComponents =
+ mAccessibilityManager.getAccessibilityShortcutTargets(
+ ACCESSIBILITY_SHORTCUT_KEY)
+ .stream()
+ .map(ComponentName::unflattenFromString)
+ .toList();
- // Should disable the corresponding service when the fragment type is
- // INVISIBLE_TOGGLE, which will enable service when the shortcut is on.
- final List<AccessibilityServiceInfo> serviceInfoList =
- mAccessibilityManager.getEnabledAccessibilityServiceList(
- AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
- serviceInfoList.forEach(info -> {
- if (getAccessibilityServiceFragmentType(info) != INVISIBLE_TOGGLE) {
- return;
- }
+ // Should disable the corresponding service when the fragment type is
+ // INVISIBLE_TOGGLE, which will enable service when the shortcut is on.
+ final List<AccessibilityServiceInfo> serviceInfoList =
+ mAccessibilityManager.getEnabledAccessibilityServiceList(
+ AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+ serviceInfoList.forEach(info -> {
+ if (getAccessibilityServiceFragmentType(info) != INVISIBLE_TOGGLE) {
+ return;
+ }
- final ComponentName serviceComponentName = info.getComponentName();
- if (hardwareKeyShortcutComponents.contains(serviceComponentName)) {
- return;
- }
+ final ComponentName serviceComponentName = info.getComponentName();
+ if (hardwareKeyShortcutComponents.contains(serviceComponentName)) {
+ return;
+ }
- setAccessibilityServiceState(getContext(), serviceComponentName, /* enabled= */
- false);
- });
+ setAccessibilityServiceState(getContext(), serviceComponentName, /* enabled= */
+ false);
+ });
+ }
mFloatingMenu.hide();
}
@@ -306,6 +319,9 @@
if (Flags.floatingMenuAnimatedTuck()) {
setClipChildren(true);
}
+ setClickable(false);
+ setFocusable(false);
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
}
@Override
@@ -430,21 +446,18 @@
}
public void onMoveToTuckedChanged(boolean moveToTuck) {
- if (Flags.floatingMenuOverlapsNavBarsFlag()) {
- if (moveToTuck) {
- final Rect bounds = mMenuViewAppearance.getWindowAvailableBounds();
- final int[] location = getLocationOnScreen();
- bounds.offset(
- location[0],
- location[1]
- );
+ if (moveToTuck) {
+ final Rect bounds = mMenuViewAppearance.getWindowAvailableBounds();
+ final int[] location = getLocationOnScreen();
+ bounds.offset(
+ location[0],
+ location[1]
+ );
- setClipBounds(bounds);
- }
- // Instead of clearing clip bounds when moveToTuck is false,
- // wait until the spring animation finishes.
+ setClipBounds(bounds);
}
- // Function is a no-operation if flag is disabled.
+ // Instead of clearing clip bounds when moveToTuck is false,
+ // wait until the spring animation finishes.
}
private void onSpringAnimationsEndAction() {
@@ -462,9 +475,7 @@
setClipBounds(null);
}
}
- if (Flags.floatingMenuImeDisplacementAnimation()) {
- mMenuView.onArrivalAtPosition(false);
- }
+ mMenuView.onArrivalAtPosition(false);
}
void dispatchAccessibilityAction(int id) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
index bc9d1ff..6b1240b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
@@ -20,11 +20,9 @@
import android.content.Context;
import android.graphics.PixelFormat;
-import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
-import com.android.systemui.Flags;
import com.android.systemui.util.settings.SecureSettings;
/**
@@ -88,14 +86,9 @@
params.privateFlags |= PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
params.windowAnimations = android.R.style.Animation_Translucent;
// Insets are configured to allow the menu to display over navigation and system bars.
- if (Flags.floatingMenuOverlapsNavBarsFlag()) {
- params.setFitInsetsTypes(0);
- params.layoutInDisplayCutoutMode =
- WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- } else {
- params.setFitInsetsTypes(
- WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
- }
+ params.setFitInsetsTypes(0);
+ params.layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
return params;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt
index b5a93b6..9f13e6d 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt
@@ -97,8 +97,8 @@
// 18% alpha black
override val fill = Color.valueOf(0f, 0f, 0f, 0.18f).toArgb()
- // GM Gray 500
- override val fillOnly = Color.parseColor("#9AA0A6")
+ // GM Gray 700
+ override val fillOnly = Color.parseColor("#5F6368")
// GM Red 600
override val errorForeground = Color.parseColor("#D93025")
@@ -117,8 +117,8 @@
// 22% alpha white
override val fill = Color.valueOf(1f, 1f, 1f, 0.22f).toArgb()
- // GM Gray 600
- override val fillOnly = Color.parseColor("#80868B")
+ // GM Gray 400
+ override val fillOnly = Color.parseColor("#BDC1C6")
// GM Red 600
override val errorForeground = Color.parseColor("#D93025")
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 9de71c1..8bd675c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -881,7 +881,7 @@
final Runnable endActionRunnable = () -> {
setVisibility(View.INVISIBLE);
- if (Flags.customBiometricPrompt()) {
+ if (Flags.customBiometricPrompt() && constraintBp()) {
mPromptSelectorInteractorProvider.get().resetPrompt();
}
removeWindowIfAttached();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
index 0c0ed77..40d38dd 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
@@ -51,6 +51,8 @@
* There is never more than one instance of the FingerprintProperty at any given time.
*/
interface FingerprintPropertyRepository {
+ /** Whether the fingerprint properties have been initialized yet. */
+ val propertiesInitialized: StateFlow<Boolean>
/** The id of fingerprint sensor. */
val sensorId: Flow<Int>
@@ -59,7 +61,7 @@
val strength: Flow<SensorStrength>
/** The types of fingerprint sensor (rear, ultrasonic, optical, etc.). */
- val sensorType: Flow<FingerprintSensorType>
+ val sensorType: StateFlow<FingerprintSensorType>
/** The sensor location relative to each physical display. */
val sensorLocations: Flow<Map<String, SensorLocationInternal>>
@@ -105,15 +107,30 @@
.stateIn(
applicationScope,
started = SharingStarted.Eagerly,
- initialValue = DEFAULT_PROPS,
+ initialValue = UNINITIALIZED_PROPS,
+ )
+
+ override val propertiesInitialized: StateFlow<Boolean> =
+ props
+ .map { it != UNINITIALIZED_PROPS }
+ .stateIn(
+ applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = props.value != UNINITIALIZED_PROPS,
)
override val sensorId: Flow<Int> = props.map { it.sensorId }
override val strength: Flow<SensorStrength> = props.map { it.sensorStrength.toSensorStrength() }
- override val sensorType: Flow<FingerprintSensorType> =
- props.map { it.sensorType.toSensorType() }
+ override val sensorType: StateFlow<FingerprintSensorType> =
+ props
+ .map { it.sensorType.toSensorType() }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = props.value.sensorType.toSensorType(),
+ )
override val sensorLocations: Flow<Map<String, SensorLocationInternal>> =
props.map {
@@ -124,6 +141,17 @@
companion object {
private const val TAG = "FingerprintPropertyRepositoryImpl"
+ private val UNINITIALIZED_PROPS =
+ FingerprintSensorPropertiesInternal(
+ -2 /* sensorId */,
+ SensorProperties.STRENGTH_CONVENIENCE,
+ 0 /* maxEnrollmentsPerUser */,
+ listOf<ComponentInfoInternal>(),
+ FingerprintSensorProperties.TYPE_UNKNOWN,
+ false /* halControlsIllumination */,
+ true /* resetLockoutRequiresHardwareAuthToken */,
+ listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT)
+ )
private val DEFAULT_PROPS =
FingerprintSensorPropertiesInternal(
-1 /* sensorId */,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
index b87fadf..4d88f49 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
@@ -18,6 +18,7 @@
import android.hardware.biometrics.Flags
import android.hardware.biometrics.PromptInfo
+import com.android.systemui.Flags.constraintBp
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.Utils.isDeviceCredentialAllowed
@@ -151,6 +152,7 @@
val hasCredentialViewShown = kind.value !is PromptKind.Biometric
val showBpForCredential =
Flags.customBiometricPrompt() &&
+ constraintBp() &&
!Utils.isBiometricAllowed(promptInfo) &&
isDeviceCredentialAllowed(promptInfo) &&
promptInfo.contentView != null
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
index ff9cdbd..5ae2ff0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
@@ -24,22 +24,35 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
@SysUISingleton
class FingerprintPropertyInteractor
@Inject
constructor(
+ @Application private val applicationScope: CoroutineScope,
@Application private val context: Context,
repository: FingerprintPropertyRepository,
configurationInteractor: ConfigurationInteractor,
displayStateInteractor: DisplayStateInteractor,
) {
- val isUdfps: Flow<Boolean> = repository.sensorType.map { it.isUdfps() }
+ val propertiesInitialized: StateFlow<Boolean> = repository.propertiesInitialized
+ val isUdfps: StateFlow<Boolean> =
+ repository.sensorType
+ .map { it.isUdfps() }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = repository.sensorType.value.isUdfps(),
+ )
/**
* Devices with multiple physical displays use unique display ids to determine which sensor is
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index e48f05d..7bb75bf 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -22,6 +22,7 @@
import android.hardware.biometrics.BiometricAuthenticator
import android.hardware.biometrics.BiometricConstants
import android.hardware.biometrics.BiometricPrompt
+import android.hardware.biometrics.Flags
import android.hardware.face.FaceManager
import android.text.method.ScrollingMovementMethod
import android.util.Log
@@ -166,11 +167,14 @@
titleView.text = viewModel.title.first()
subtitleView.text = viewModel.subtitle.first()
descriptionView.text = viewModel.description.first()
- BiometricCustomizedViewBinder.bind(
- customizedViewContainer,
- view.requireViewById(R.id.space_above_content),
- viewModel
- )
+
+ if (Flags.customBiometricPrompt() && constraintBp()) {
+ BiometricCustomizedViewBinder.bind(
+ customizedViewContainer,
+ view.requireViewById(R.id.space_above_content),
+ viewModel
+ )
+ }
// set button listeners
negativeButton.setOnClickListener { legacyCallback.onButtonNegative() }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
index 3469cfa..e457601 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
@@ -86,7 +86,12 @@
launch {
var width = 0
var height = 0
- viewModel.activeAuthType.collect { activeAuthType ->
+ combine(promptViewModel.size, viewModel.activeAuthType, ::Pair).collect {
+ (_, activeAuthType) ->
+ // Every time after bp shows, [isIconViewLoaded] is set to false in
+ // [BiometricViewSizeBinder]. Then when biometric prompt view is redrew
+ // (when size or activeAuthType changes), we need to update
+ // [isIconViewLoaded] here to keep it correct.
when (activeAuthType) {
AuthType.Fingerprint,
AuthType.Coex -> {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
index 9c28f1c..9949e4c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -141,6 +141,9 @@
/** Hide the side fingerprint sensor indicator */
private fun hide() {
if (overlayView != null) {
+ val lottie = overlayView!!.requireViewById<LottieAnimationView>(R.id.sidefps_animation)
+ lottie.pauseAnimation()
+ lottie.removeAllLottieOnCompositionLoadedListener()
windowManager.get().removeView(overlayView)
overlayView = null
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 61aeffe..86b0b44 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -29,6 +29,7 @@
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import com.android.systemui.Flags.bpTalkback
+import com.android.systemui.Flags.constraintBp
import com.android.systemui.biometrics.UdfpsUtils
import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
@@ -217,18 +218,12 @@
*/
val faceMode: Flow<Boolean> =
combine(modalities, isConfirmationRequired, fingerprintStartMode) {
- modalities: BiometricModalities,
- isConfirmationRequired: Boolean,
- fingerprintStartMode: FingerprintStartMode ->
- if (modalities.hasFaceAndFingerprint) {
- if (isConfirmationRequired) {
- false
- } else {
- !fingerprintStartMode.isStarted
- }
- } else {
- false
- }
+ modalities,
+ isConfirmationRequired,
+ fingerprintStartMode ->
+ modalities.hasFaceAndFingerprint &&
+ !isConfirmationRequired &&
+ fingerprintStartMode == FingerprintStartMode.Pending
}
.distinctUntilChanged()
@@ -248,14 +243,11 @@
* asset to be loaded before determining the prompt size.
*/
val isIconViewLoaded: Flow<Boolean> =
- combine(credentialKind, _isIconViewLoaded.asStateFlow()) { credentialKind, isIconViewLoaded
- ->
- if (credentialKind is PromptKind.Biometric) {
- isIconViewLoaded
- } else {
- true
+ combine(modalities, _isIconViewLoaded.asStateFlow()) { modalities, isIconViewLoaded ->
+ val noIcon = modalities.isEmpty
+ noIcon || isIconViewLoaded
}
- }
+ .distinctUntilChanged()
// Sets whether the prompt's iconView animation has been loaded in the view yet.
fun setIsIconViewLoaded(iconViewLoaded: Boolean) {
@@ -284,7 +276,7 @@
promptSelectorInteractor.prompt
.map {
when {
- !customBiometricPrompt() || it == null -> null
+ !(customBiometricPrompt() && constraintBp()) || it == null -> null
it.logoRes != -1 -> context.resources.getDrawable(it.logoRes, context.theme)
it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap)
else ->
@@ -304,7 +296,7 @@
promptSelectorInteractor.prompt
.map {
when {
- !customBiometricPrompt() || it == null -> ""
+ !(customBiometricPrompt() && constraintBp()) || it == null -> ""
it.logoDescription != null -> it.logoDescription
else ->
try {
@@ -329,7 +321,7 @@
/** Custom content view for the prompt. */
val contentView: Flow<PromptContentView?> =
promptSelectorInteractor.prompt
- .map { if (customBiometricPrompt()) it?.contentView else null }
+ .map { if (customBiometricPrompt() && constraintBp()) it?.contentView else null }
.distinctUntilChanged()
private val originalDescription =
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
index c25e748..7f6fc91 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
@@ -23,10 +23,12 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.Flags
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.biometrics.data.repository.FacePropertyRepository
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.bouncer.data.repository.BouncerMessageRepository
import com.android.systemui.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.bouncer.shared.model.BouncerMessageStrings
import com.android.systemui.bouncer.shared.model.Message
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -35,46 +37,6 @@
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.res.R.string.bouncer_face_not_recognized
-import com.android.systemui.res.R.string.keyguard_enter_password
-import com.android.systemui.res.R.string.keyguard_enter_pattern
-import com.android.systemui.res.R.string.keyguard_enter_pin
-import com.android.systemui.res.R.string.kg_bio_too_many_attempts_password
-import com.android.systemui.res.R.string.kg_bio_too_many_attempts_pattern
-import com.android.systemui.res.R.string.kg_bio_too_many_attempts_pin
-import com.android.systemui.res.R.string.kg_bio_try_again_or_password
-import com.android.systemui.res.R.string.kg_bio_try_again_or_pattern
-import com.android.systemui.res.R.string.kg_bio_try_again_or_pin
-import com.android.systemui.res.R.string.kg_face_locked_out
-import com.android.systemui.res.R.string.kg_fp_not_recognized
-import com.android.systemui.res.R.string.kg_primary_auth_locked_out_password
-import com.android.systemui.res.R.string.kg_primary_auth_locked_out_pattern
-import com.android.systemui.res.R.string.kg_primary_auth_locked_out_pin
-import com.android.systemui.res.R.string.kg_prompt_after_adaptive_auth_lock
-import com.android.systemui.res.R.string.kg_prompt_after_dpm_lock
-import com.android.systemui.res.R.string.kg_prompt_after_update_password
-import com.android.systemui.res.R.string.kg_prompt_after_update_pattern
-import com.android.systemui.res.R.string.kg_prompt_after_update_pin
-import com.android.systemui.res.R.string.kg_prompt_after_user_lockdown_password
-import com.android.systemui.res.R.string.kg_prompt_after_user_lockdown_pattern
-import com.android.systemui.res.R.string.kg_prompt_after_user_lockdown_pin
-import com.android.systemui.res.R.string.kg_prompt_auth_timeout
-import com.android.systemui.res.R.string.kg_prompt_password_auth_timeout
-import com.android.systemui.res.R.string.kg_prompt_pattern_auth_timeout
-import com.android.systemui.res.R.string.kg_prompt_pin_auth_timeout
-import com.android.systemui.res.R.string.kg_prompt_reason_restart_password
-import com.android.systemui.res.R.string.kg_prompt_reason_restart_pattern
-import com.android.systemui.res.R.string.kg_prompt_reason_restart_pin
-import com.android.systemui.res.R.string.kg_prompt_unattended_update
-import com.android.systemui.res.R.string.kg_too_many_failed_attempts_countdown
-import com.android.systemui.res.R.string.kg_trust_agent_disabled
-import com.android.systemui.res.R.string.kg_unlock_with_password_or_fp
-import com.android.systemui.res.R.string.kg_unlock_with_pattern_or_fp
-import com.android.systemui.res.R.string.kg_unlock_with_pin_or_fp
-import com.android.systemui.res.R.string.kg_wrong_input_try_fp_suggestion
-import com.android.systemui.res.R.string.kg_wrong_password_try_again
-import com.android.systemui.res.R.string.kg_wrong_pattern_try_again
-import com.android.systemui.res.R.string.kg_wrong_pin_try_again
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.Quint
import javax.inject.Inject
@@ -130,17 +92,22 @@
repository.setMessage(
when (biometricSourceType) {
BiometricSourceType.FINGERPRINT ->
- incorrectFingerprintInput(currentSecurityMode)
+ BouncerMessageStrings.incorrectFingerprintInput(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
BiometricSourceType.FACE ->
- incorrectFaceInput(
- currentSecurityMode,
- isFingerprintAuthCurrentlyAllowed.value
- )
+ BouncerMessageStrings.incorrectFaceInput(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
else ->
- defaultMessage(
- currentSecurityMode,
- isFingerprintAuthCurrentlyAllowed.value
- )
+ BouncerMessageStrings.defaultMessage(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
}
)
}
@@ -189,45 +156,79 @@
trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterReboot
) {
if (wasRebootedForMainlineUpdate) {
- authRequiredForMainlineUpdate(currentSecurityMode)
+ BouncerMessageStrings.authRequiredForMainlineUpdate(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
} else {
- authRequiredAfterReboot(currentSecurityMode)
+ BouncerMessageStrings.authRequiredAfterReboot(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
}
} else if (trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterTimeout) {
- authRequiredAfterPrimaryAuthTimeout(currentSecurityMode)
+ BouncerMessageStrings.authRequiredAfterPrimaryAuthTimeout(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
} else if (flags.isPrimaryAuthRequiredAfterDpmLockdown) {
- authRequiredAfterAdminLockdown(currentSecurityMode)
+ BouncerMessageStrings.authRequiredAfterAdminLockdown(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
} else if (
- trustOrBiometricsAvailable && flags.primaryAuthRequiredForUnattendedUpdate
+ trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredForUnattendedUpdate
) {
- authRequiredForUnattendedUpdate(currentSecurityMode)
+ BouncerMessageStrings.authRequiredForUnattendedUpdate(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
} else if (fpLockedOut) {
- class3AuthLockedOut(currentSecurityMode)
+ BouncerMessageStrings.class3AuthLockedOut(currentSecurityMode.toAuthModel())
+ .toMessage()
} else if (faceLockedOut) {
if (isFaceAuthClass3) {
- class3AuthLockedOut(currentSecurityMode)
+ BouncerMessageStrings.class3AuthLockedOut(currentSecurityMode.toAuthModel())
+ .toMessage()
} else {
- faceLockedOut(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+ BouncerMessageStrings.faceLockedOut(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
}
} else if (flags.isSomeAuthRequiredAfterAdaptiveAuthRequest) {
- authRequiredAfterAdaptiveAuthRequest(
- currentSecurityMode,
- isFingerprintAuthCurrentlyAllowed.value
- )
+ BouncerMessageStrings.authRequiredAfterAdaptiveAuthRequest(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
} else if (
trustOrBiometricsAvailable &&
flags.strongerAuthRequiredAfterNonStrongBiometricsTimeout
) {
- nonStrongAuthTimeout(
- currentSecurityMode,
- isFingerprintAuthCurrentlyAllowed.value
- )
+ BouncerMessageStrings.nonStrongAuthTimeout(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
} else if (isTrustUsuallyManaged && flags.someAuthRequiredAfterUserRequest) {
- trustAgentDisabled(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+ BouncerMessageStrings.trustAgentDisabled(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
} else if (isTrustUsuallyManaged && flags.someAuthRequiredAfterTrustAgentExpired) {
- trustAgentDisabled(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+ BouncerMessageStrings.trustAgentDisabled(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
} else if (trustOrBiometricsAvailable && flags.isInUserLockdown) {
- authRequiredAfterUserLockdown(currentSecurityMode)
+ BouncerMessageStrings.authRequiredAfterUserLockdown(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
} else {
defaultMessage
}
@@ -244,7 +245,11 @@
override fun onTick(millisUntilFinished: Long) {
val secondsRemaining = (millisUntilFinished / 1000.0).roundToInt()
- val message = primaryAuthLockedOut(currentSecurityMode)
+ val message =
+ BouncerMessageStrings.primaryAuthLockedOut(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
message.message?.animate = false
message.message?.formatterArgs =
mutableMapOf<String, Any>(Pair("count", secondsRemaining))
@@ -258,7 +263,11 @@
if (!Flags.revampedBouncerMessages()) return
repository.setMessage(
- incorrectSecurityInput(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+ BouncerMessageStrings.incorrectSecurityInput(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
)
}
@@ -285,7 +294,12 @@
}
private val defaultMessage: BouncerMessageModel
- get() = defaultMessage(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+ get() =
+ BouncerMessageStrings.defaultMessage(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
fun onPrimaryBouncerUserInput() {
if (!Flags.revampedBouncerMessages()) return
@@ -354,283 +368,35 @@
return BouncerMessageModel(
message =
Message(
- messageResId = defaultMessage(securityMode, fpAuthIsAllowed).message?.messageResId,
+ messageResId =
+ BouncerMessageStrings.defaultMessage(
+ securityMode.toAuthModel(),
+ fpAuthIsAllowed
+ )
+ .toMessage()
+ .message
+ ?.messageResId,
animate = false
),
secondaryMessage = Message(message = secondaryMessage, animate = false)
)
}
-private fun defaultMessage(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) {
- defaultMessageWithFingerprint(securityMode)
- } else
- when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
- SecurityMode.Password -> Pair(keyguard_enter_password, 0)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun defaultMessageWithFingerprint(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, 0)
- SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, 0)
- SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, 0)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun incorrectSecurityInput(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) {
- incorrectSecurityInputWithFingerprint(securityMode)
- } else
- when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, 0)
- SecurityMode.Password -> Pair(kg_wrong_password_try_again, 0)
- SecurityMode.PIN -> Pair(kg_wrong_pin_try_again, 0)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun incorrectSecurityInputWithFingerprint(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, kg_wrong_input_try_fp_suggestion)
- SecurityMode.Password -> Pair(kg_wrong_password_try_again, kg_wrong_input_try_fp_suggestion)
- SecurityMode.PIN -> Pair(kg_wrong_pin_try_again, kg_wrong_input_try_fp_suggestion)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun incorrectFingerprintInput(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pattern)
- SecurityMode.Password -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_password)
- SecurityMode.PIN -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun incorrectFaceInput(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) incorrectFaceInputWithFingerprintAllowed(securityMode)
- else
- when (securityMode) {
- SecurityMode.Pattern -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pattern)
- SecurityMode.Password -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_password)
- SecurityMode.PIN -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun incorrectFaceInputWithFingerprintAllowed(
- securityMode: SecurityMode
-): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, bouncer_face_not_recognized)
- SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, bouncer_face_not_recognized)
- SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, bouncer_face_not_recognized)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun biometricLockout(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_bio_too_many_attempts_pattern)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_bio_too_many_attempts_password)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_bio_too_many_attempts_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredAfterReboot(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_reason_restart_pattern)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_reason_restart_password)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredAfterAdminLockdown(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_dpm_lock)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_after_dpm_lock)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredAfterAdaptiveAuthRequest(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) authRequiredAfterAdaptiveAuthRequestFingerprintAllowed(securityMode)
- else
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_adaptive_auth_lock)
- SecurityMode.Password ->
- Pair(keyguard_enter_password, kg_prompt_after_adaptive_auth_lock)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_adaptive_auth_lock)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredAfterAdaptiveAuthRequestFingerprintAllowed(
- securityMode: SecurityMode
-): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern ->
- Pair(kg_unlock_with_pattern_or_fp, kg_prompt_after_adaptive_auth_lock)
- SecurityMode.Password ->
- Pair(kg_unlock_with_password_or_fp, kg_prompt_after_adaptive_auth_lock)
- SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_prompt_after_adaptive_auth_lock)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredAfterUserLockdown(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_user_lockdown_pattern)
- SecurityMode.Password ->
- Pair(keyguard_enter_password, kg_prompt_after_user_lockdown_password)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredForUnattendedUpdate(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_unattended_update)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_unattended_update)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_unattended_update)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredForMainlineUpdate(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_update_pattern)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_after_update_password)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_update_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredAfterPrimaryAuthTimeout(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_pattern_auth_timeout)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_password_auth_timeout)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun nonStrongAuthTimeout(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) {
- nonStrongAuthTimeoutWithFingerprintAllowed(securityMode)
- } else
- when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_auth_timeout)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_auth_timeout)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_auth_timeout)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-fun nonStrongAuthTimeoutWithFingerprintAllowed(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_prompt_auth_timeout)
- SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_prompt_auth_timeout)
- SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_prompt_auth_timeout)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun faceLockedOut(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) faceLockedOutButFingerprintAvailable(securityMode)
- else
- when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_face_locked_out)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_face_locked_out)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_face_locked_out)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun faceLockedOutButFingerprintAvailable(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_face_locked_out)
- SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_face_locked_out)
- SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_face_locked_out)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun class3AuthLockedOut(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_bio_too_many_attempts_pattern)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_bio_too_many_attempts_password)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_bio_too_many_attempts_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun trustAgentDisabled(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) trustAgentDisabledWithFingerprintAllowed(securityMode)
- else
- when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_trust_agent_disabled)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_trust_agent_disabled)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_trust_agent_disabled)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun trustAgentDisabledWithFingerprintAllowed(
- securityMode: SecurityMode
-): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_trust_agent_disabled)
- SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_trust_agent_disabled)
- SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_trust_agent_disabled)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun primaryAuthLockedOut(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern ->
- Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pattern)
- SecurityMode.Password ->
- Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_password)
- SecurityMode.PIN ->
- Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
private fun Pair<Int, Int>.toMessage(): BouncerMessageModel {
return BouncerMessageModel(
message = Message(messageResId = this.first, animate = false),
secondaryMessage = Message(messageResId = this.second, animate = false)
)
}
+
+private fun SecurityMode.toAuthModel(): AuthenticationMethodModel {
+ return when (this) {
+ SecurityMode.Invalid -> AuthenticationMethodModel.None
+ SecurityMode.None -> AuthenticationMethodModel.None
+ SecurityMode.Pattern -> AuthenticationMethodModel.Pattern
+ SecurityMode.Password -> AuthenticationMethodModel.Password
+ SecurityMode.PIN -> AuthenticationMethodModel.Pin
+ SecurityMode.SimPin -> AuthenticationMethodModel.Sim
+ SecurityMode.SimPuk -> AuthenticationMethodModel.Sim
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
index c3d4cb3..7d3075a 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
@@ -44,6 +44,8 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -70,6 +72,9 @@
val isLockedEsim: StateFlow<Boolean?> = repository.isLockedEsim
val errorDialogMessage: StateFlow<String?> = repository.errorDialogMessage
+ private val _bouncerMessageChanged = MutableSharedFlow<String?>()
+ val bouncerMessageChanged: SharedFlow<String?> = _bouncerMessageChanged
+
/** Returns the default message for the sim pin screen. */
fun getDefaultMessage(): String {
val isEsimLocked = repository.isLockedEsim.value ?: false
@@ -81,7 +86,7 @@
return ""
}
- var count = telephonyManager.activeModemCount
+ val count = telephonyManager.activeModemCount
val info: SubscriptionInfo? = repository.activeSubscriptionInfo.value
val displayName = info?.displayName
var msg: String =
@@ -156,32 +161,24 @@
repository.setSimVerificationErrorMessage(null)
}
- /**
- * Based on sim state, unlock the locked sim with the given credentials.
- *
- * @return Any message that should show associated with the provided input. Null means that no
- * message needs to be shown.
- */
- suspend fun verifySim(input: List<Any>): String? {
+ /** Based on sim state, unlock the locked sim with the given credentials. */
+ suspend fun verifySim(input: List<Any>) {
+ val code = input.joinToString(separator = "")
if (repository.isSimPukLocked.value) {
- return verifySimPuk(input.joinToString(separator = ""))
+ verifySimPuk(code)
+ } else {
+ verifySimPin(code)
}
-
- return verifySimPin(input.joinToString(separator = ""))
}
- /**
- * Verifies the input and unlocks the locked sim with a 4-8 digit pin code.
- *
- * @return Any message that should show associated with the provided input. Null means that no
- * message needs to be shown.
- */
- private suspend fun verifySimPin(input: String): String? {
+ /** Verifies the input and unlocks the locked sim with a 4-8 digit pin code. */
+ private suspend fun verifySimPin(input: String) {
val subscriptionId = repository.subscriptionId.value
// A SIM PIN is 4 to 8 decimal digits according to
// GSM 02.17 version 5.0.1, Section 5.6 PIN Management
if (input.length < MIN_SIM_PIN_LENGTH || input.length > MAX_SIM_PIN_LENGTH) {
- return resources.getString(R.string.kg_invalid_sim_pin_hint)
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_invalid_sim_pin_hint))
+ return
}
val result =
withContext(backgroundDispatcher) {
@@ -190,8 +187,10 @@
telephonyManager.supplyIccLockPin(input)
}
when (result.result) {
- PinResult.PIN_RESULT_TYPE_SUCCESS ->
+ PinResult.PIN_RESULT_TYPE_SUCCESS -> {
keyguardUpdateMonitor.reportSimUnlocked(subscriptionId)
+ _bouncerMessageChanged.emit(null)
+ }
PinResult.PIN_RESULT_TYPE_INCORRECT -> {
if (result.attemptsRemaining <= CRITICAL_NUM_OF_ATTEMPTS) {
// Show a dialog to display the remaining number of attempts to verify the sim
@@ -199,24 +198,22 @@
repository.setSimVerificationErrorMessage(
getPinPasswordErrorMessage(result.attemptsRemaining)
)
+ _bouncerMessageChanged.emit(null)
} else {
- return getPinPasswordErrorMessage(result.attemptsRemaining)
+ _bouncerMessageChanged.emit(
+ getPinPasswordErrorMessage(result.attemptsRemaining)
+ )
}
}
}
-
- return null
}
/**
* Verifies the input and unlocks the locked sim with a puk code instead of pin.
*
* This occurs after incorrectly verifying the sim pin multiple times.
- *
- * @return Any message that should show associated with the provided input. Null means that no
- * message needs to be shown.
*/
- private suspend fun verifySimPuk(entry: String): String? {
+ private suspend fun verifySimPuk(entry: String) {
val (enteredSimPuk, enteredSimPin) = repository.simPukInputModel
val subscriptionId: Int = repository.subscriptionId.value
@@ -224,10 +221,11 @@
if (enteredSimPuk == null) {
if (entry.length >= MIN_SIM_PUK_LENGTH) {
repository.setSimPukUserInput(enteredSimPuk = entry)
- return resources.getString(R.string.kg_puk_enter_pin_hint)
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_puk_enter_pin_hint))
} else {
- return resources.getString(R.string.kg_invalid_sim_puk_hint)
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_invalid_sim_puk_hint))
}
+ return
}
// Stage 2: Set a new sim pin to lock the sim card.
@@ -237,10 +235,11 @@
enteredSimPuk = enteredSimPuk,
enteredSimPin = entry,
)
- return resources.getString(R.string.kg_enter_confirm_pin_hint)
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_enter_confirm_pin_hint))
} else {
- return resources.getString(R.string.kg_invalid_sim_pin_hint)
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_invalid_sim_pin_hint))
}
+ return
}
// Stage 3: Confirm the newly set sim pin.
@@ -250,7 +249,8 @@
resources.getString(R.string.kg_invalid_confirm_pin_hint)
)
repository.setSimPukUserInput(enteredSimPuk = enteredSimPuk)
- return resources.getString(R.string.kg_puk_enter_pin_hint)
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_puk_enter_pin_hint))
+ return
}
val result =
@@ -261,9 +261,11 @@
resetSimPukUserInput()
when (result.result) {
- PinResult.PIN_RESULT_TYPE_SUCCESS ->
+ PinResult.PIN_RESULT_TYPE_SUCCESS -> {
keyguardUpdateMonitor.reportSimUnlocked(subscriptionId)
- PinResult.PIN_RESULT_TYPE_INCORRECT ->
+ _bouncerMessageChanged.emit(null)
+ }
+ PinResult.PIN_RESULT_TYPE_INCORRECT -> {
if (result.attemptsRemaining <= CRITICAL_NUM_OF_ATTEMPTS) {
// Show a dialog to display the remaining number of attempts to verify the sim
// puk to the user.
@@ -274,17 +276,21 @@
isEsimLocked = repository.isLockedEsim.value == true
)
)
+ _bouncerMessageChanged.emit(null)
} else {
- return getPukPasswordErrorMessage(
- result.attemptsRemaining,
- isDefault = false,
- isEsimLocked = repository.isLockedEsim.value == true
+ _bouncerMessageChanged.emit(
+ getPukPasswordErrorMessage(
+ result.attemptsRemaining,
+ isDefault = false,
+ isEsimLocked = repository.isLockedEsim.value == true
+ )
)
}
- else -> return resources.getString(R.string.kg_password_puk_failed)
+ }
+ else -> {
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_password_puk_failed))
+ }
}
-
- return null
}
private fun getPinPasswordErrorMessage(attemptsRemaining: Int): String {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt
new file mode 100644
index 0000000..cb12ce5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.shared.model
+
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin
+import com.android.systemui.res.R
+
+typealias BouncerMessagePair = Pair<Int, Int>
+
+val BouncerMessagePair.primaryMessage: Int
+ get() = this.first
+
+val BouncerMessagePair.secondaryMessage: Int
+ get() = this.second
+
+object BouncerMessageStrings {
+ private val EmptyMessage = Pair(0, 0)
+
+ fun defaultMessage(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), 0)
+ Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), 0)
+ Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), 0)
+ else -> EmptyMessage
+ }
+ }
+
+ fun incorrectSecurityInput(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ val secondaryMessage = incorrectSecurityInputSecondaryMessage(fpAuthIsAllowed)
+ return when (securityMode) {
+ Pattern -> Pair(R.string.kg_wrong_pattern_try_again, secondaryMessage)
+ Password -> Pair(R.string.kg_wrong_password_try_again, secondaryMessage)
+ Pin -> Pair(R.string.kg_wrong_pin_try_again, secondaryMessage)
+ else -> EmptyMessage
+ }
+ }
+
+ private fun incorrectSecurityInputSecondaryMessage(fpAuthIsAllowed: Boolean): Int {
+ return if (fpAuthIsAllowed) R.string.kg_wrong_input_try_fp_suggestion else 0
+ }
+
+ fun incorrectFingerprintInput(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+ val primaryMessage = R.string.kg_fp_not_recognized
+ return when (securityMode) {
+ Pattern -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pattern)
+ Password -> Pair(primaryMessage, R.string.kg_bio_try_again_or_password)
+ Pin -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pin)
+ else -> EmptyMessage
+ }
+ }
+
+ fun incorrectFaceInput(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ return if (fpAuthIsAllowed) incorrectFaceInputWithFingerprintAllowed(securityMode)
+ else {
+ val primaryMessage = R.string.bouncer_face_not_recognized
+ when (securityMode) {
+ Pattern -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pattern)
+ Password -> Pair(primaryMessage, R.string.kg_bio_try_again_or_password)
+ Pin -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pin)
+ else -> EmptyMessage
+ }
+ }
+ }
+
+ private fun incorrectFaceInputWithFingerprintAllowed(
+ securityMode: AuthenticationMethodModel
+ ): BouncerMessagePair {
+ val secondaryMsg = R.string.bouncer_face_not_recognized
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(true), secondaryMsg)
+ Password -> Pair(passwordDefaultMessage(true), secondaryMsg)
+ Pin -> Pair(pinDefaultMessage(true), secondaryMsg)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredAfterReboot(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_reason_restart_pattern)
+ Password ->
+ Pair(passwordDefaultMessage(false), R.string.kg_prompt_reason_restart_password)
+ Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_reason_restart_pin)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredAfterAdminLockdown(
+ securityMode: AuthenticationMethodModel
+ ): BouncerMessagePair {
+ val secondaryMsg = R.string.kg_prompt_after_dpm_lock
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(false), secondaryMsg)
+ Password -> Pair(passwordDefaultMessage(false), secondaryMsg)
+ Pin -> Pair(pinDefaultMessage(false), secondaryMsg)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredAfterAdaptiveAuthRequest(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ val secondaryMsg = R.string.kg_prompt_after_adaptive_auth_lock
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredAfterUserLockdown(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern ->
+ Pair(patternDefaultMessage(false), R.string.kg_prompt_after_user_lockdown_pattern)
+ Password ->
+ Pair(passwordDefaultMessage(false), R.string.kg_prompt_after_user_lockdown_password)
+ Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_after_user_lockdown_pin)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredForUnattendedUpdate(
+ securityMode: AuthenticationMethodModel
+ ): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_added_security_pattern)
+ Password ->
+ Pair(passwordDefaultMessage(false), R.string.kg_prompt_added_security_password)
+ Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_added_security_pin)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredForMainlineUpdate(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_after_update_pattern)
+ Password ->
+ Pair(passwordDefaultMessage(false), R.string.kg_prompt_after_update_password)
+ Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_after_update_pin)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredAfterPrimaryAuthTimeout(
+ securityMode: AuthenticationMethodModel
+ ): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_pattern_auth_timeout)
+ Password ->
+ Pair(passwordDefaultMessage(false), R.string.kg_prompt_password_auth_timeout)
+ Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_pin_auth_timeout)
+ else -> EmptyMessage
+ }
+ }
+
+ fun nonStrongAuthTimeout(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ val secondaryMsg = R.string.kg_prompt_auth_timeout
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ else -> EmptyMessage
+ }
+ }
+
+ fun faceLockedOut(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ val secondaryMsg = R.string.kg_face_locked_out
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ else -> EmptyMessage
+ }
+ }
+
+ fun class3AuthLockedOut(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(false), R.string.kg_bio_too_many_attempts_pattern)
+ Password ->
+ Pair(passwordDefaultMessage(false), R.string.kg_bio_too_many_attempts_password)
+ Pin -> Pair(pinDefaultMessage(false), R.string.kg_bio_too_many_attempts_pin)
+ else -> EmptyMessage
+ }
+ }
+
+ fun trustAgentDisabled(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ val secondaryMsg = R.string.kg_trust_agent_disabled
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ else -> EmptyMessage
+ }
+ }
+
+ fun primaryAuthLockedOut(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern ->
+ Pair(
+ R.string.kg_too_many_failed_attempts_countdown,
+ R.string.kg_primary_auth_locked_out_pattern
+ )
+ Password ->
+ Pair(
+ R.string.kg_too_many_failed_attempts_countdown,
+ R.string.kg_primary_auth_locked_out_password
+ )
+ Pin ->
+ Pair(
+ R.string.kg_too_many_failed_attempts_countdown,
+ R.string.kg_primary_auth_locked_out_pin
+ )
+ else -> EmptyMessage
+ }
+ }
+
+ private fun patternDefaultMessage(fingerprintAllowed: Boolean): Int {
+ return if (fingerprintAllowed) R.string.kg_unlock_with_pattern_or_fp
+ else R.string.keyguard_enter_pattern
+ }
+
+ private fun pinDefaultMessage(fingerprintAllowed: Boolean): Int {
+ return if (fingerprintAllowed) R.string.kg_unlock_with_pin_or_fp
+ else R.string.keyguard_enter_pin
+ }
+
+ private fun passwordDefaultMessage(fingerprintAllowed: Boolean): Int {
+ return if (fingerprintAllowed) R.string.kg_unlock_with_password_or_fp
+ else R.string.keyguard_enter_password
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 7f4a029..e910a92 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -157,8 +157,7 @@
if (authenticationMethod == AuthenticationMethodModel.Sim) {
viewModelScope.launch {
isSimUnlockingDialogVisible.value = true
- val msg = simBouncerInteractor.verifySim(getInput())
- interactor.setMessage(msg)
+ simBouncerInteractor.verifySim(getInput())
isSimUnlockingDialogVisible.value = false
clearInput()
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index 3819e61..4f4f3d0 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -97,6 +97,14 @@
}
};
+ private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
+ new KeyguardStateController.Callback() {
+ @Override
+ public void onKeyguardShowingChanged() {
+ updateSensorRegistration();
+ }
+ };
+
private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
new KeyguardUpdateMonitorCallback() {
@@ -176,6 +184,8 @@
mStatusBarStateController.addCallback(mStatusBarStateListener);
mState = mStatusBarStateController.getState();
+ mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
+
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
mJavaAdapter.alwaysCollectFlow(
@@ -364,6 +374,24 @@
} else {
sessionEnd();
}
+ updateSensorRegistration();
+ }
+
+ private boolean shouldBeRegisteredToSensors() {
+ return mScreenOn
+ && (mState == StatusBarState.KEYGUARD
+ || (mState == StatusBarState.SHADE
+ && mKeyguardStateController.isOccluded()
+ && mKeyguardStateController.isShowing()))
+ && !mShowingAod;
+ }
+
+ private void updateSensorRegistration() {
+ if (shouldBeRegisteredToSensors()) {
+ registerSensors();
+ } else {
+ unregisterSensors();
+ }
}
private void sessionStart() {
@@ -371,7 +399,6 @@
logDebug("Starting Session");
mSessionStarted = true;
mFalsingDataProvider.setJustUnlockedWithFace(false);
- registerSensors();
mFalsingDataProvider.onSessionStarted();
}
}
@@ -380,7 +407,6 @@
if (mSessionStarted) {
logDebug("Ending Session");
mSessionStarted = false;
- unregisterSensors();
mFalsingDataProvider.onSessionEnd();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
new file mode 100644
index 0000000..9e7fb4e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal
+
+import android.annotation.SuppressLint
+import android.app.DreamManager
+import com.android.systemui.CoreStartable
+import com.android.systemui.Flags.communalHub
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.Utils.Companion.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+/**
+ * A [CoreStartable] responsible for automatically starting the dream when the communal hub is
+ * shown, to support the user swiping away the hub to enter the dream.
+ */
+@SysUISingleton
+class CommunalDreamStartable
+@Inject
+constructor(
+ private val powerInteractor: PowerInteractor,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ private val dreamManager: DreamManager,
+ @Background private val bgScope: CoroutineScope,
+) : CoreStartable {
+ @SuppressLint("MissingPermission")
+ override fun start() {
+ if (!communalHub()) {
+ return
+ }
+
+ // Restart the dream underneath the hub in order to support the ability to swipe
+ // away the hub to enter the dream.
+ keyguardTransitionInteractor.finishedKeyguardState
+ .sample(powerInteractor.isAwake, keyguardInteractor.isDreaming)
+ .onEach { (finishedState, isAwake, dreaming) ->
+ if (
+ finishedState == KeyguardState.GLANCEABLE_HUB &&
+ !dreaming &&
+ dreamManager.canStartDreaming(isAwake)
+ ) {
+ dreamManager.startDream()
+ }
+ }
+ .launchIn(bgScope)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index c3c7411..98c8205 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal
+import android.provider.Settings
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.CoreStartable
import com.android.systemui.communal.domain.interactor.CommunalInteractor
@@ -24,25 +25,32 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dock.DockManager
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.kotlin.emitOnStart
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import com.android.systemui.util.settings.SystemSettings
import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
/**
* A [CoreStartable] responsible for automatically navigating between communal scenes when certain
* conditions are met.
*/
-@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class CommunalSceneStartable
@Inject
@@ -50,9 +58,13 @@
private val dockManager: DockManager,
private val communalInteractor: CommunalInteractor,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val systemSettings: SystemSettings,
@Application private val applicationScope: CoroutineScope,
@Background private val bgScope: CoroutineScope,
) : CoreStartable {
+ private var screenTimeout: Int = DEFAULT_SCREEN_TIMEOUT
+
override fun start() {
// Handle automatically switching based on keyguard state.
keyguardTransitionInteractor.startedKeyguardTransitionStep
@@ -78,6 +90,43 @@
// }
// }
// .launchIn(bgScope)
+
+ systemSettings
+ .observerFlow(Settings.System.SCREEN_OFF_TIMEOUT)
+ // Read the setting value on start.
+ .emitOnStart()
+ .onEach {
+ screenTimeout =
+ systemSettings.getInt(
+ Settings.System.SCREEN_OFF_TIMEOUT,
+ DEFAULT_SCREEN_TIMEOUT
+ )
+ }
+ .launchIn(bgScope)
+
+ // Handle timing out back to the dream.
+ bgScope.launch {
+ combine(
+ communalInteractor.desiredScene,
+ keyguardInteractor.isDreaming,
+ // Emit a value on start so the combine starts.
+ communalInteractor.userActivity.emitOnStart()
+ ) { scene, isDreaming, _ ->
+ // Time out should run whenever we're dreaming and the hub is open, even if not
+ // docked.
+ scene == CommunalScenes.Communal && isDreaming
+ }
+ // collectLatest cancels the previous action block when new values arrive, so any
+ // already running timeout gets cancelled when conditions change or user interaction
+ // is detected.
+ .collectLatest { shouldTimeout ->
+ if (!shouldTimeout) {
+ return@collectLatest
+ }
+ delay(screenTimeout.milliseconds)
+ communalInteractor.onSceneChanged(CommunalScenes.Blank)
+ }
+ }
}
private suspend fun determineSceneAfterTransition(
@@ -105,5 +154,6 @@
companion object {
val AWAKE_DEBOUNCE_DELAY = 5.seconds
val DOCK_DEBOUNCE_DELAY = 1.seconds
+ val DEFAULT_SCREEN_TIMEOUT = 15000
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 8142957..52025b1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -18,9 +18,14 @@
import android.app.smartspace.SmartspaceTarget
import android.content.ComponentName
+import android.content.Intent
+import android.content.IntentFilter
import android.os.UserHandle
+import android.os.UserManager
+import android.provider.Settings
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.communal.data.repository.CommunalMediaRepository
import com.android.systemui.communal.data.repository.CommunalPrefsRepository
import com.android.systemui.communal.data.repository.CommunalRepository
@@ -45,6 +50,7 @@
import com.android.systemui.log.dagger.CommunalTableLog
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
@@ -53,13 +59,17 @@
import com.android.systemui.util.kotlin.BooleanFlowOperators.and
import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import com.android.systemui.util.kotlin.BooleanFlowOperators.or
+import com.android.systemui.util.kotlin.emitOnStart
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -77,7 +87,8 @@
class CommunalInteractor
@Inject
constructor(
- @Application applicationScope: CoroutineScope,
+ @Application val applicationScope: CoroutineScope,
+ broadcastDispatcher: BroadcastDispatcher,
private val communalRepository: CommunalRepository,
private val widgetRepository: CommunalWidgetRepository,
private val communalPrefsRepository: CommunalPrefsRepository,
@@ -88,6 +99,8 @@
private val appWidgetHost: CommunalAppWidgetHost,
private val editWidgetsActivityStarter: EditWidgetsActivityStarter,
private val userTracker: UserTracker,
+ private val activityStarter: ActivityStarter,
+ private val userManager: UserManager,
sceneInteractor: SceneInteractor,
sceneContainerFlags: SceneContainerFlags,
@CommunalLog logBuffer: LogBuffer,
@@ -142,6 +155,14 @@
/** Transition state of the hub mode. */
val transitionState: StateFlow<ObservableTransitionState> = communalRepository.transitionState
+ val _userActivity: MutableSharedFlow<Unit> =
+ MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+ val userActivity: Flow<Unit> = _userActivity.asSharedFlow()
+
+ fun signalUserInteraction() {
+ _userActivity.tryEmit(Unit)
+ }
+
/**
* Updates the transition state of the hub [SceneTransitionLayout].
*
@@ -247,6 +268,18 @@
editWidgetsActivityStarter.startActivity(preselectedKey)
}
+ /**
+ * Navigates to communal widget setting after user has unlocked the device. Currently, this
+ * setting resides within the Hub Mode settings screen.
+ */
+ fun navigateToCommunalWidgetSettings() {
+ activityStarter.postStartActivityDismissingKeyguard(
+ Intent(Settings.ACTION_COMMUNAL_SETTING)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP),
+ /* delay= */ 0,
+ )
+ }
+
/** Dismiss the CTA tile from the hub in view mode. */
suspend fun dismissCtaTile() = communalPrefsRepository.setCtaDismissedForCurrentUser()
@@ -272,6 +305,33 @@
fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) =
widgetRepository.updateWidgetOrder(widgetIdToPriorityMap)
+ /** Request to unpause work profile that is currently in quiet mode. */
+ fun unpauseWorkProfile() {
+ userTracker.userProfiles
+ .find { it.isManagedProfile }
+ ?.userHandle
+ ?.let { userHandle ->
+ userManager.requestQuietModeEnabled(/* enableQuietMode */ false, userHandle)
+ }
+ }
+
+ /** Returns true if work profile is in quiet mode (disabled) for user handle. */
+ private fun isQuietModeEnabled(userHandle: UserHandle): Boolean =
+ userManager.isManagedProfile(userHandle.identifier) &&
+ userManager.isQuietModeEnabled(userHandle)
+
+ /** Emits whenever a work profile pause or unpause broadcast is received. */
+ private val updateOnWorkProfileBroadcastReceived: Flow<Unit> =
+ broadcastDispatcher
+ .broadcastFlow(
+ filter =
+ IntentFilter().apply {
+ addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
+ addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
+ },
+ )
+ .emitOnStart()
+
/** All widgets present in db. */
val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
isCommunalAvailable.flatMapLatest { available ->
@@ -282,8 +342,9 @@
val widgetContent: Flow<List<WidgetContent>> =
combine(
widgetRepository.communalWidgets.map { filterWidgetsByExistingUsers(it) },
- communalSettingsInteractor.communalWidgetCategories
- ) { widgets, allowedCategories ->
+ communalSettingsInteractor.communalWidgetCategories,
+ updateOnWorkProfileBroadcastReceived,
+ ) { widgets, allowedCategories, _ ->
widgets.map { widget ->
if (widget.providerInfo.widgetCategory and allowedCategories != 0) {
// At least one category this widget specified is allowed, so show it
@@ -291,6 +352,7 @@
appWidgetId = widget.appWidgetId,
providerInfo = widget.providerInfo,
appWidgetHost = appWidgetHost,
+ inQuietMode = isQuietModeEnabled(widget.providerInfo.profile)
)
} else {
WidgetContent.DisabledWidget(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index c64f666..5fabd3c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -51,6 +51,7 @@
override val appWidgetId: Int,
override val providerInfo: AppWidgetProviderInfo,
val appWidgetHost: CommunalAppWidgetHost,
+ val inQuietMode: Boolean,
) : WidgetContent {
override val key = KEY.widget(appWidgetId)
// Widget size is always half.
@@ -152,4 +153,6 @@
}
fun isWidgetContent() = this is WidgetContent
+
+ fun isSmartspace() = this is Smartspace
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 35372cd..c913300 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -45,6 +45,10 @@
val selectedKey: StateFlow<String?>
get() = _selectedKey
+ fun signalUserInteraction() {
+ communalInteractor.signalUserInteraction()
+ }
+
fun onSceneChanged(scene: SceneKey) {
communalInteractor.onSceneChanged(scene)
}
@@ -70,6 +74,10 @@
communalInteractor.addWidget(componentName, user, priority, configurator)
}
+ open fun onOpenEnableWidgetDialog() {}
+
+ open fun onOpenEnableWorkProfileDialog() {}
+
/** A list of all the communal content to be displayed in the communal hub. */
abstract val communalContent: Flow<List<CommunalContentModel>>
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index fc9a7df..6e69ed7 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -21,6 +21,7 @@
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
@@ -46,6 +47,7 @@
import kotlinx.coroutines.launch
/** The default view model used for showing the communal hub. */
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class CommunalViewModel
@Inject
@@ -54,6 +56,7 @@
private val communalInteractor: CommunalInteractor,
tutorialInteractor: CommunalTutorialInteractor,
shadeInteractor: ShadeInteractor,
+ deviceEntryInteractor: DeviceEntryInteractor,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
@CommunalLog logBuffer: LogBuffer,
) : BaseCommunalViewModel(communalInteractor, mediaHost) {
@@ -84,9 +87,19 @@
override val isPopupOnDismissCtaShowing: Flow<Boolean> =
_isPopupOnDismissCtaShowing.asStateFlow()
+ private val _isEnableWidgetDialogShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ val isEnableWidgetDialogShowing: Flow<Boolean> = _isEnableWidgetDialogShowing.asStateFlow()
+
+ private val _isEnableWorkProfileDialogShowing: MutableStateFlow<Boolean> =
+ MutableStateFlow(false)
+ val isEnableWorkProfileDialogShowing: Flow<Boolean> =
+ _isEnableWorkProfileDialogShowing.asStateFlow()
+
/** Whether touches should be disabled in communal */
val touchesAllowed: Flow<Boolean> = not(shadeInteractor.isAnyFullyExpanded)
+ val deviceUnlocked: Flow<Boolean> = deviceEntryInteractor.isUnlocked
+
init {
// Initialize our media host for the UMO. This only needs to happen once and must be done
// before the MediaHierarchyManager attempts to move the UMO to the hub.
@@ -115,6 +128,40 @@
setPopupOnDismissCtaVisibility(false)
}
+ override fun onOpenEnableWidgetDialog() {
+ setIsEnableWidgetDialogShowing(true)
+ }
+
+ fun onEnableWidgetDialogConfirm() {
+ communalInteractor.navigateToCommunalWidgetSettings()
+ setIsEnableWidgetDialogShowing(false)
+ }
+
+ fun onEnableWidgetDialogCancel() {
+ setIsEnableWidgetDialogShowing(false)
+ }
+
+ override fun onOpenEnableWorkProfileDialog() {
+ setIsEnableWorkProfileDialogShowing(true)
+ }
+
+ fun onEnableWorkProfileDialogConfirm() {
+ communalInteractor.unpauseWorkProfile()
+ setIsEnableWorkProfileDialogShowing(false)
+ }
+
+ fun onEnableWorkProfileDialogCancel() {
+ setIsEnableWorkProfileDialogShowing(false)
+ }
+
+ private fun setIsEnableWidgetDialogShowing(isVisible: Boolean) {
+ _isEnableWidgetDialogShowing.value = isVisible
+ }
+
+ private fun setIsEnableWorkProfileDialogShowing(isVisible: Boolean) {
+ _isEnableWorkProfileDialogShowing.value = isVisible
+ }
+
private fun setPopupOnDismissCtaVisibility(isVisible: Boolean) {
_isPopupOnDismissCtaShowing.value = isVisible
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index ce24259..9fa3e5f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -643,6 +643,7 @@
@Provides
@Singleton
+ @Nullable
static CarrierConfigManager provideCarrierConfigManager(Context context) {
return context.getSystemService(CarrierConfigManager.class);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index a4011fd..1003050 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -69,6 +69,7 @@
import com.android.systemui.toast.ToastModule;
import com.android.systemui.unfold.SysUIUnfoldStartableModule;
import com.android.systemui.unfold.UnfoldTransitionModule;
+import com.android.systemui.util.kotlin.SysUICoroutinesModule;
import com.android.systemui.volume.dagger.VolumeModule;
import com.android.systemui.wallpapers.dagger.WallpaperModule;
@@ -117,6 +118,7 @@
ShadeModule.class,
StartCentralSurfacesModule.class,
SceneContainerFrameworkModule.class,
+ SysUICoroutinesModule.class,
SysUIUnfoldStartableModule.class,
UnfoldTransitionModule.Startables.class,
ToastModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index a3d6ad4..21ee5bd 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -24,6 +24,7 @@
import com.android.systemui.back.domain.interactor.BackActionInteractor
import com.android.systemui.biometrics.BiometricNotificationService
import com.android.systemui.clipboardoverlay.ClipboardListener
+import com.android.systemui.communal.CommunalDreamStartable
import com.android.systemui.communal.CommunalSceneStartable
import com.android.systemui.communal.log.CommunalLoggerStartable
import com.android.systemui.communal.widgets.CommunalAppWidgetHostStartable
@@ -328,6 +329,11 @@
@Binds
@IntoMap
+ @ClassKey(CommunalDreamStartable::class)
+ abstract fun bindCommunalDreamStartable(impl: CommunalDreamStartable): CoreStartable
+
+ @Binds
+ @IntoMap
@ClassKey(CommunalAppWidgetHostStartable::class)
abstract fun bindCommunalAppWidgetHostStartable(
impl: CommunalAppWidgetHostStartable
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt
index 96171aa..d495fac 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt
@@ -18,6 +18,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -31,9 +32,22 @@
constructor(
repository: BiometricSettingsRepository,
) {
+
+ /**
+ * Flags that control the device entry authentication behavior.
+ *
+ * This exposes why biometrics may not be currently allowed.
+ */
+ val authenticationFlags: Flow<AuthenticationFlags> = repository.authenticationFlags
+
+ /** Whether the current user has enrolled and enabled fingerprint auth. */
+ val isFingerprintAuthEnrolledAndEnabled: Flow<Boolean> =
+ repository.isFingerprintEnrolledAndEnabled
+
val fingerprintAuthCurrentlyAllowed: Flow<Boolean> =
repository.isFingerprintAuthCurrentlyAllowed
- val faceAuthEnrolledAndEnabled: Flow<Boolean> = repository.isFaceAuthEnrolledAndEnabled
+ /** Whether the current user has enrolled and enabled face auth. */
+ val isFaceAuthEnrolledAndEnabled: Flow<Boolean> = repository.isFaceAuthEnrolledAndEnabled
val faceAuthCurrentlyAllowed: Flow<Boolean> = repository.isFaceAuthCurrentlyAllowed
/** Whether both fingerprint and face are enrolled and enabled for device entry. */
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
index 99bd25b..7733de4 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
@@ -32,6 +32,10 @@
/** Current detection status */
val detectionStatus: Flow<FaceDetectionStatus>
+ val lockedOut: Flow<Boolean>
+
+ val authenticated: Flow<Boolean>
+
/** Can face auth be run right now */
fun canFaceAuthRun(): Boolean
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
index a5f6f7c..8059993 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
@@ -16,6 +16,8 @@
package com.android.systemui.deviceentry.domain.interactor
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
@@ -23,14 +25,20 @@
import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.map
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class DeviceEntryFingerprintAuthInteractor
@Inject
constructor(
repository: DeviceEntryFingerprintAuthRepository,
+ biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
+ fingerprintPropertyRepository: FingerprintPropertyRepository,
) {
/** Whether fingerprint authentication is currently running or not */
val isRunning: Flow<Boolean> = repository.isRunning
@@ -47,4 +55,21 @@
repository.authenticationStatus.filterIsInstance<ErrorFingerprintAuthenticationStatus>()
val fingerprintHelp: Flow<HelpFingerprintAuthenticationStatus> =
repository.authenticationStatus.filterIsInstance<HelpFingerprintAuthenticationStatus>()
+
+ /**
+ * Whether fingerprint authentication is currently allowed for the user. This is true if the
+ * user has fingerprint auth enabled, enrolled, it is not disabled by any security timeouts by
+ * [com.android.systemui.keyguard.shared.model.AuthenticationFlags] and not locked out due to
+ * too many incorrect attempts.
+ */
+ val isFingerprintAuthCurrentlyAllowed: Flow<Boolean> =
+ combine(isLockedOut, biometricSettingsInteractor.fingerprintAuthCurrentlyAllowed, ::Pair)
+ .map { (lockedOut, currentlyAllowed) -> !lockedOut && currentlyAllowed }
+
+ /**
+ * Whether the fingerprint sensor is present under the display as opposed to being on the power
+ * button or behind/rear of the phone.
+ */
+ val isSensorUnderDisplay =
+ fingerprintPropertyRepository.sensorType.map(FingerprintSensorType::isUdfps)
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 029a4f3..fa2421a 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -16,24 +16,30 @@
package com.android.systemui.deviceentry.domain.interactor
+import androidx.annotation.VisibleForTesting
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
-import com.android.systemui.keyguard.data.repository.TrustRepository
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
+import com.android.systemui.flags.SystemPropertiesHelper
+import com.android.systemui.keyguard.domain.interactor.TrustInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.util.kotlin.Quad
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
@@ -55,10 +61,13 @@
private val repository: DeviceEntryRepository,
private val authenticationInteractor: AuthenticationInteractor,
private val sceneInteractor: SceneInteractor,
- deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository,
- trustRepository: TrustRepository,
+ faceAuthInteractor: DeviceEntryFaceAuthInteractor,
+ private val fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
+ private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
+ private val trustInteractor: TrustInteractor,
flags: SceneContainerFlags,
deviceUnlockedInteractor: DeviceUnlockedInteractor,
+ private val systemPropertiesHelper: SystemPropertiesHelper,
) {
/**
* Whether the device is unlocked.
@@ -96,8 +105,8 @@
*/
private val isPassivelyAuthenticated =
merge(
- trustRepository.isCurrentUserTrusted,
- deviceEntryFaceAuthRepository.isAuthenticated,
+ trustInteractor.isTrusted,
+ faceAuthInteractor.authenticated,
)
.onStart { emit(false) }
@@ -134,6 +143,67 @@
initialValue = null,
)
+ private val faceEnrolledAndEnabled = biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
+ private val fingerprintEnrolledAndEnabled =
+ biometricSettingsInteractor.isFingerprintAuthEnrolledAndEnabled
+ private val trustAgentEnabled = trustInteractor.isEnrolledAndEnabled
+
+ private val faceOrFingerprintOrTrustEnabled: Flow<Triple<Boolean, Boolean, Boolean>> =
+ combine(faceEnrolledAndEnabled, fingerprintEnrolledAndEnabled, trustAgentEnabled, ::Triple)
+
+ /**
+ * Reason why device entry is restricted to certain authentication methods for the current user.
+ *
+ * Emits null when there are no device entry restrictions active.
+ */
+ val deviceEntryRestrictionReason: Flow<DeviceEntryRestrictionReason?> =
+ faceOrFingerprintOrTrustEnabled.flatMapLatest {
+ (faceEnabled, fingerprintEnabled, trustEnabled) ->
+ if (faceEnabled || fingerprintEnabled || trustEnabled) {
+ combine(
+ biometricSettingsInteractor.authenticationFlags,
+ faceAuthInteractor.lockedOut,
+ fingerprintAuthInteractor.isLockedOut,
+ trustInteractor.isTrustAgentCurrentlyAllowed,
+ ::Quad
+ )
+ .map { (authFlags, isFaceLockedOut, isFingerprintLockedOut, trustManaged) ->
+ when {
+ authFlags.isPrimaryAuthRequiredAfterReboot &&
+ wasRebootedForMainlineUpdate ->
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate
+ authFlags.isPrimaryAuthRequiredAfterReboot ->
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot
+ authFlags.isPrimaryAuthRequiredAfterDpmLockdown ->
+ DeviceEntryRestrictionReason.PolicyLockdown
+ authFlags.isInUserLockdown -> DeviceEntryRestrictionReason.UserLockdown
+ authFlags.isPrimaryAuthRequiredForUnattendedUpdate ->
+ DeviceEntryRestrictionReason.UnattendedUpdate
+ authFlags.isPrimaryAuthRequiredAfterTimeout ->
+ DeviceEntryRestrictionReason.SecurityTimeout
+ authFlags.isPrimaryAuthRequiredAfterLockout ->
+ DeviceEntryRestrictionReason.BouncerLockedOut
+ isFingerprintLockedOut ->
+ DeviceEntryRestrictionReason.StrongBiometricsLockedOut
+ isFaceLockedOut && faceAuthInteractor.isFaceAuthStrong() ->
+ DeviceEntryRestrictionReason.StrongBiometricsLockedOut
+ isFaceLockedOut -> DeviceEntryRestrictionReason.NonStrongFaceLockedOut
+ authFlags.isSomeAuthRequiredAfterAdaptiveAuthRequest ->
+ DeviceEntryRestrictionReason.AdaptiveAuthRequest
+ (trustEnabled && !trustManaged) &&
+ (authFlags.someAuthRequiredAfterTrustAgentExpired ||
+ authFlags.someAuthRequiredAfterUserRequest) ->
+ DeviceEntryRestrictionReason.TrustAgentDisabled
+ authFlags.strongerAuthRequiredAfterNonStrongBiometricsTimeout ->
+ DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout
+ else -> null
+ }
+ }
+ } else {
+ flowOf(null)
+ }
+ }
+
/**
* Attempt to enter the device and dismiss the lockscreen. If authentication is required to
* unlock the device it will transition to bouncer.
@@ -187,4 +257,12 @@
}
}
}
+
+ private val wasRebootedForMainlineUpdate
+ get() = systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE
+
+ companion object {
+ @VisibleForTesting const val SYS_BOOT_REASON_PROP = "sys.boot.reason.last"
+ @VisibleForTesting const val REBOOT_MAINLINE_UPDATE = "reboot,mainline_update"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt
index 72b9da6..80b52ed 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt
@@ -16,17 +16,17 @@
package com.android.systemui.deviceentry.domain.interactor
-import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
/** Encapsulates business logic for device entry under-display fingerprint state changes. */
@ExperimentalCoroutinesApi
@@ -34,14 +34,13 @@
class DeviceEntryUdfpsInteractor
@Inject
constructor(
+ fingerprintPropertyInteractor: FingerprintPropertyInteractor,
// TODO (b/309655554): create & use interactors for these repositories
- fingerprintPropertyRepository: FingerprintPropertyRepository,
fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
biometricSettingsRepository: BiometricSettingsRepository,
) {
/** Whether the device supports an under display fingerprint sensor. */
- val isUdfpsSupported: Flow<Boolean> =
- fingerprintPropertyRepository.sensorType.map { it.isUdfps() }
+ val isUdfpsSupported: StateFlow<Boolean> = fingerprintPropertyInteractor.isUdfps
/** Whether the under-display fingerprint sensor is enrolled and enabled for device entry. */
val isUdfpsEnrolledAndEnabled: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt
index fd6fbc9..98deda09 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt
@@ -77,7 +77,7 @@
private fun startUpdatingFaceHelpMessageDeferral() {
scope.launch {
- biometricSettingsInteractor.faceAuthEnrolledAndEnabled
+ biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
.flatMapLatest { faceEnrolledAndEnabled ->
if (faceEnrolledAndEnabled) {
faceAcquired
@@ -94,7 +94,7 @@
}
scope.launch {
- biometricSettingsInteractor.faceAuthEnrolledAndEnabled
+ biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
.flatMapLatest { faceEnrolledAndEnabled ->
if (faceEnrolledAndEnabled) {
faceHelp
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
index 3b94166..65f3eb7 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
@@ -31,10 +31,10 @@
*/
@SysUISingleton
class NoopDeviceEntryFaceAuthInteractor @Inject constructor() : DeviceEntryFaceAuthInteractor {
- override val authenticationStatus: Flow<FaceAuthenticationStatus>
- get() = emptyFlow()
- override val detectionStatus: Flow<FaceDetectionStatus>
- get() = emptyFlow()
+ override val authenticationStatus: Flow<FaceAuthenticationStatus> = emptyFlow()
+ override val detectionStatus: Flow<FaceDetectionStatus> = emptyFlow()
+ override val lockedOut: Flow<Boolean> = emptyFlow()
+ override val authenticated: Flow<Boolean> = emptyFlow()
override fun canFaceAuthRun(): Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index 0c9fbc2..a7266503 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -272,6 +272,8 @@
/** Provide the status of face detection */
override val detectionStatus = repository.detectionStatus
+ override val lockedOut: Flow<Boolean> = repository.isLockedOut
+ override val authenticated: Flow<Boolean> = repository.isAuthenticated
private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) {
if (repository.isLockedOut.value) {
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceEntryRestrictionReason.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceEntryRestrictionReason.kt
new file mode 100644
index 0000000..5b672ac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceEntryRestrictionReason.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.shared.model
+
+/** List of reasons why device entry can be restricted to certain authentication methods. */
+enum class DeviceEntryRestrictionReason {
+ /**
+ * Reason: Lockdown initiated by the user.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ UserLockdown,
+
+ /**
+ * Reason: Not unlocked since reboot.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ DeviceNotUnlockedSinceReboot,
+
+ /**
+ * Reason: Not unlocked since reboot after a mainline update.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ DeviceNotUnlockedSinceMainlineUpdate,
+
+ /**
+ * Reason: Lockdown initiated by admin through installed device policy
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ PolicyLockdown,
+
+ /**
+ * Reason: Device entry credentials need to be used for an unattended update at a later point in
+ * time.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ UnattendedUpdate,
+
+ /**
+ * Reason: Device was not unlocked using PIN/Pattern/Password for a prolonged period of time.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ SecurityTimeout,
+
+ /**
+ * Reason: A "class 3"/strong biometrics device entry method was locked out after many incorrect
+ * authentication attempts.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ *
+ * @see
+ * [Biometric classes](https://source.android.com/docs/security/features/biometric/measure#biometric-classes)
+ */
+ StrongBiometricsLockedOut,
+
+ /**
+ * Reason: A weak (class 2)/convenience (class 3) strength face biometrics device entry method
+ * was locked out after many incorrect authentication attempts.
+ *
+ * Restriction: Only stronger authentication methods (class 3 or bouncer) are allowed.
+ *
+ * @see
+ * [Biometric classes](https://source.android.com/docs/security/features/biometric/measure#biometric-classes)
+ */
+ NonStrongFaceLockedOut,
+
+ /**
+ * Reason: Device was last unlocked using a weak/convenience strength biometrics device entry
+ * method and a stronger authentication method wasn't used to unlock the device for a prolonged
+ * period of time.
+ *
+ * Restriction: Only stronger authentication methods (class 3 or bouncer) are allowed.
+ *
+ * @see
+ * [Biometric classes](https://source.android.com/docs/security/features/biometric/measure#biometric-classes)
+ */
+ NonStrongBiometricsSecurityTimeout,
+
+ /**
+ * Reason: A trust agent that was granting trust has either expired or disabled by the user by
+ * opening the power menu.
+ *
+ * Restriction: Only non trust agent device entry methods are allowed.
+ */
+ TrustAgentDisabled,
+
+ /**
+ * Reason: Theft protection is enabled after too many unlock attempts.
+ *
+ * Restriction: Only stronger authentication methods (class 3 or bouncer) are allowed.
+ */
+ AdaptiveAuthRequest,
+
+ /**
+ * Reason: Bouncer was locked out after too many incorrect authentication attempts.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ BouncerLockedOut,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index b97bace..f860893 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -33,7 +33,7 @@
import com.android.systemui.complication.ComplicationLayoutParams.POSITION_TOP
import com.android.systemui.complication.ComplicationLayoutParams.Position
import com.android.systemui.dreams.dagger.DreamOverlayModule
-import com.android.systemui.dreams.ui.viewmodel.DreamOverlayViewModel
+import com.android.systemui.dreams.ui.viewmodel.DreamViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
@@ -53,7 +53,7 @@
private val mStatusBarViewController: DreamOverlayStatusBarViewController,
private val mOverlayStateController: DreamOverlayStateController,
@Named(DreamOverlayModule.DREAM_BLUR_RADIUS) private val mDreamBlurRadius: Int,
- private val dreamOverlayViewModel: DreamOverlayViewModel,
+ private val dreamViewModel: DreamViewModel,
@Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION)
private val mDreamInBlurAnimDurationMs: Long,
@Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
@@ -87,7 +87,7 @@
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
- dreamOverlayViewModel.dreamOverlayTranslationY.collect { px ->
+ dreamViewModel.dreamOverlayTranslationY.collect { px ->
ComplicationLayoutParams.iteratePositions(
{ position: Int -> setElementsTranslationYAtPosition(px, position) },
POSITION_TOP or POSITION_BOTTOM
@@ -96,7 +96,7 @@
}
launch {
- dreamOverlayViewModel.dreamOverlayTranslationX.collect { px ->
+ dreamViewModel.dreamOverlayTranslationX.collect { px ->
ComplicationLayoutParams.iteratePositions(
{ position: Int -> setElementsTranslationXAtPosition(px, position) },
POSITION_TOP or POSITION_BOTTOM
@@ -105,7 +105,7 @@
}
launch {
- dreamOverlayViewModel.dreamOverlayAlpha.collect { alpha ->
+ dreamViewModel.dreamOverlayAlpha.collect { alpha ->
ComplicationLayoutParams.iteratePositions(
{ position: Int ->
setElementsAlphaAtPosition(
@@ -120,7 +120,7 @@
}
launch {
- dreamOverlayViewModel.transitionEnded.collect { _ ->
+ dreamViewModel.transitionEnded.collect { _ ->
mOverlayStateController.setExitAnimationsRunning(false)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
index e74814a..376d312 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
@@ -17,6 +17,7 @@
package com.android.systemui.dreams.homecontrols
import android.content.Intent
+import android.os.PowerManager
import android.service.controls.ControlsProviderService
import android.service.dreams.DreamService
import android.window.TaskFragmentInfo
@@ -27,6 +28,8 @@
import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor.Companion.MAX_UPDATE_CORRELATION_DELAY
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.dagger.DreamLog
+import com.android.systemui.util.wakelock.WakeLock
+import com.android.systemui.util.wakelock.WakeLock.Builder.NO_TIMEOUT
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
@@ -42,14 +45,23 @@
private val controlsSettingsRepository: ControlsSettingsRepository,
private val taskFragmentFactory: TaskFragmentComponent.Factory,
private val homeControlsComponentInteractor: HomeControlsComponentInteractor,
+ private val wakeLockBuilder: WakeLock.Builder,
private val dreamActivityProvider: DreamActivityProvider,
@Background private val bgDispatcher: CoroutineDispatcher,
@DreamLog logBuffer: LogBuffer
) : DreamService() {
+
private val serviceJob = SupervisorJob()
private val serviceScope = CoroutineScope(bgDispatcher + serviceJob)
- private val logger = DreamLogger(logBuffer, "HomeControlsDreamService")
+ private val logger = DreamLogger(logBuffer, TAG)
private lateinit var taskFragmentComponent: TaskFragmentComponent
+ private val wakeLock: WakeLock by lazy {
+ wakeLockBuilder
+ .setMaxTimeout(NO_TIMEOUT)
+ .setTag(TAG)
+ .setLevelsAndFlags(PowerManager.SCREEN_BRIGHT_WAKE_LOCK)
+ .build()
+ }
override fun onAttachedToWindow() {
super.onAttachedToWindow()
@@ -72,6 +84,8 @@
hide = { finish() }
)
.apply { createTaskFragment() }
+
+ wakeLock.acquire(TAG)
}
private fun onTaskFragmentInfoChanged(taskFragmentInfo: TaskFragmentInfo) {
@@ -100,6 +114,7 @@
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
+ wakeLock.release(TAG)
taskFragmentComponent.destroy()
serviceScope.launch {
delay(CANCELLATION_DELAY_AFTER_DETACHED)
@@ -115,5 +130,6 @@
* complete.
*/
val CANCELLATION_DELAY_AFTER_DETACHED = 5.seconds
+ const val TAG = "HomeControlsDreamService"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt
index 6cd94c6..1452526 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt
@@ -30,7 +30,7 @@
class HomeControlsDreamStartable
@Inject
constructor(
- private val context: Context,
+ context: Context,
private val packageManager: PackageManager,
private val homeControlsComponentInteractor: HomeControlsComponentInteractor,
@Background private val bgScope: CoroutineScope,
@@ -39,10 +39,13 @@
private val componentName = ComponentName(context, HomeControlsDreamService::class.java)
override fun start() {
- if (!homePanelDream()) return
bgScope.launch {
- homeControlsComponentInteractor.panelComponent.collect { selectedPanelComponent ->
- setEnableHomeControlPanel(selectedPanelComponent != null)
+ if (homePanelDream()) {
+ homeControlsComponentInteractor.panelComponent.collect { selectedPanelComponent ->
+ setEnableHomeControlPanel(selectedPanelComponent != null)
+ }
+ } else {
+ setEnableHomeControlPanel(false)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamOverlayViewModel.kt
deleted file mode 100644
index bd99f4b..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamOverlayViewModel.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.dreams.ui.viewmodel
-
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
-import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel
-import com.android.systemui.res.R
-import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.merge
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SysUISingleton
-class DreamOverlayViewModel
-@Inject
-constructor(
- configurationInteractor: ConfigurationInteractor,
- toGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
- fromGlanceableHubTransitionInteractor: GlanceableHubToDreamingTransitionViewModel,
- private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
-) {
-
- val dreamOverlayTranslationX: Flow<Float> =
- merge(
- toGlanceableHubTransitionViewModel.dreamOverlayTranslationX,
- fromGlanceableHubTransitionInteractor.dreamOverlayTranslationX,
- )
-
- val dreamOverlayTranslationY: Flow<Float> =
- configurationInteractor
- .dimensionPixelSize(R.dimen.dream_overlay_exit_y_offset)
- .flatMapLatest { px: Int ->
- toLockscreenTransitionViewModel.dreamOverlayTranslationY(px)
- }
-
- val dreamOverlayAlpha: Flow<Float> =
- merge(
- toLockscreenTransitionViewModel.dreamOverlayAlpha,
- toGlanceableHubTransitionViewModel.dreamOverlayAlpha,
- fromGlanceableHubTransitionInteractor.dreamOverlayAlpha,
- )
-
- val transitionEnded = toLockscreenTransitionViewModel.transitionEnded
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
new file mode 100644
index 0000000..0cb57fb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.ui.viewmodel
+
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dock.DockManager
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel
+import com.android.systemui.res.R
+import com.android.systemui.settings.UserTracker
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.merge
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class DreamViewModel
+@Inject
+constructor(
+ configurationInteractor: ConfigurationInteractor,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ fromGlanceableHubTransitionInteractor: GlanceableHubToDreamingTransitionViewModel,
+ private val toGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
+ private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
+ private val dockManager: DockManager,
+ private val communalInteractor: CommunalInteractor,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val userTracker: UserTracker,
+) {
+
+ fun startTransitionFromDream() {
+ val showGlanceableHub =
+ dockManager.isDocked &&
+ communalInteractor.isCommunalEnabled.value &&
+ !keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId)
+ if (showGlanceableHub) {
+ toGlanceableHubTransitionViewModel.startTransition()
+ communalInteractor.onSceneChanged(CommunalScenes.Communal)
+ } else {
+ toLockscreenTransitionViewModel.startTransition()
+ }
+ }
+
+ val dreamOverlayTranslationX: Flow<Float> =
+ merge(
+ toGlanceableHubTransitionViewModel.dreamOverlayTranslationX,
+ fromGlanceableHubTransitionInteractor.dreamOverlayTranslationX,
+ )
+ .distinctUntilChanged()
+
+ val dreamOverlayTranslationY: Flow<Float> =
+ configurationInteractor
+ .dimensionPixelSize(R.dimen.dream_overlay_exit_y_offset)
+ .flatMapLatest { px: Int ->
+ toLockscreenTransitionViewModel.dreamOverlayTranslationY(px)
+ }
+
+ val dreamAlpha: Flow<Float> =
+ merge(
+ toLockscreenTransitionViewModel.dreamOverlayAlpha,
+ toGlanceableHubTransitionViewModel.dreamAlpha,
+ )
+ .distinctUntilChanged()
+
+ val dreamOverlayAlpha: Flow<Float> =
+ merge(
+ toLockscreenTransitionViewModel.dreamOverlayAlpha,
+ toGlanceableHubTransitionViewModel.dreamOverlayAlpha,
+ fromGlanceableHubTransitionInteractor.dreamOverlayAlpha,
+ )
+ .distinctUntilChanged()
+
+ val transitionEnded =
+ keyguardTransitionInteractor.fromDreamingTransition.filter { step ->
+ step.transitionState == TransitionState.FINISHED ||
+ step.transitionState == TransitionState.CANCELED
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 6bb84649..a199fea 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -76,23 +76,6 @@
val NOTIFICATION_MEMORY_LOGGING_ENABLED =
releasedFlag("notification_memory_logging_enabled")
- // TODO(b/260335638): Tracking Bug
- @JvmField
- val NOTIFICATION_INLINE_REPLY_ANIMATION = releasedFlag("notification_inline_reply_animation")
-
- // TODO(b/288326013): Tracking Bug
- @JvmField
- val NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION =
- unreleasedFlag("notification_async_hybrid_view_inflation", teamfood = false)
-
- @JvmField
- val ANIMATED_NOTIFICATION_SHADE_INSETS =
- releasedFlag("animated_notification_shade_insets")
-
- // TODO(b/268005230): Tracking Bug
- @JvmField
- val SENSITIVE_REVEAL_ANIM = releasedFlag("sensitive_reveal_anim")
-
// TODO(b/280783617): Tracking Bug
@Keep
@JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
index 6fa20de..1e3c604 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
@@ -20,36 +20,34 @@
import javax.inject.Inject
import javax.inject.Singleton
-/**
- * Proxy to make {@link SystemProperties} easily testable.
- */
+/** Proxy to make {@link SystemProperties} easily testable. */
@Singleton
open class SystemPropertiesHelper @Inject constructor() {
- fun get(name: String): String {
+ open fun get(name: String): String {
return SystemProperties.get(name)
}
- fun get(name: String, def: String?): String {
+ open fun get(name: String, def: String?): String {
return SystemProperties.get(name, def)
}
- fun getBoolean(name: String, default: Boolean): Boolean {
+ open fun getBoolean(name: String, default: Boolean): Boolean {
return SystemProperties.getBoolean(name, default)
}
- fun setBoolean(name: String, value: Boolean) {
+ open fun setBoolean(name: String, value: Boolean) {
SystemProperties.set(name, if (value) "1" else "0")
}
- fun set(name: String, value: String) {
+ open fun set(name: String, value: String) {
SystemProperties.set(name, value)
}
- fun set(name: String, value: Int) {
+ open fun set(name: String, value: Int) {
set(name, value.toString())
}
- fun erase(name: String) {
+ open fun erase(name: String) {
set(name, "")
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/LongPressHapticBuilder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/LongPressHapticBuilder.kt
new file mode 100644
index 0000000..0143b85
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/LongPressHapticBuilder.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.qs
+
+import android.os.VibrationEffect
+import android.util.Log
+import kotlin.math.max
+
+object LongPressHapticBuilder {
+
+ const val INVALID_DURATION = 0 /* in ms */
+
+ private const val TAG = "LongPressHapticBuilder"
+ private const val SPIN_SCALE = 0.2f
+ private const val CLICK_SCALE = 0.5f
+ private const val LOW_TICK_SCALE = 0.08f
+ private const val WARMUP_TIME = 75 /* in ms */
+ private const val DAMPING_TIME = 24 /* in ms */
+
+ /** Create the signal that indicates that a long-press action is available. */
+ fun createLongPressHint(
+ lowTickDuration: Int,
+ spinDuration: Int,
+ effectDuration: Int
+ ): VibrationEffect? {
+ if (lowTickDuration == 0 || spinDuration == 0) {
+ Log.d(
+ TAG,
+ "The LOW_TICK and/or SPIN primitives are not supported. No signal created.",
+ )
+ return null
+ }
+ if (effectDuration < WARMUP_TIME + spinDuration + DAMPING_TIME) {
+ Log.d(
+ TAG,
+ "Cannot fit long-press hint signal in the effect duration. No signal created",
+ )
+ return null
+ }
+
+ val nLowTicks = WARMUP_TIME / lowTickDuration
+ val rampDownLowTicks = DAMPING_TIME / lowTickDuration
+ val composition = VibrationEffect.startComposition()
+
+ // Warmup low ticks
+ repeat(nLowTicks) {
+ composition.addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+ LOW_TICK_SCALE,
+ 0,
+ )
+ }
+
+ // Spin effect
+ composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, SPIN_SCALE, 0)
+
+ // Damping low ticks
+ repeat(rampDownLowTicks) { i ->
+ composition.addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+ LOW_TICK_SCALE / (i + 1),
+ 0,
+ )
+ }
+
+ return composition.compose()
+ }
+
+ /** Create a "snapping" effect that triggers at the end of a long-press gesture */
+ fun createSnapEffect(): VibrationEffect? =
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, CLICK_SCALE, 0)
+ .compose()
+
+ /** Creates a signal that indicates the reversal of the long-press animation. */
+ fun createReversedEffect(
+ pausedProgress: Float,
+ lowTickDuration: Int,
+ effectDuration: Int,
+ ): VibrationEffect? {
+ val duration = pausedProgress * effectDuration
+ if (duration == 0f) return null
+
+ if (lowTickDuration == 0) {
+ Log.d(TAG, "Cannot play reverse haptics because LOW_TICK is not supported")
+ return null
+ }
+
+ val nLowTicks = (duration / lowTickDuration).toInt()
+ if (nLowTicks == 0) return null
+
+ val composition = VibrationEffect.startComposition()
+ var scale: Float
+ val step = LOW_TICK_SCALE / nLowTicks
+ repeat(nLowTicks) { i ->
+ scale = max(LOW_TICK_SCALE - step * i, 0f)
+ composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, scale, 0)
+ }
+ return composition.compose()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
new file mode 100644
index 0000000..ec72a14
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.qs
+
+import android.animation.ValueAnimator
+import android.annotation.SuppressLint
+import android.os.VibrationEffect
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewConfiguration
+import android.view.animation.AccelerateDecelerateInterpolator
+import androidx.annotation.VisibleForTesting
+import androidx.core.animation.doOnCancel
+import androidx.core.animation.doOnEnd
+import androidx.core.animation.doOnStart
+import com.android.systemui.statusbar.VibratorHelper
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+
+/**
+ * A class that handles the long press visuo-haptic effect for a QS tile.
+ *
+ * The class is also a [View.OnTouchListener] to handle the touch events, clicks and long-press
+ * gestures of the tile. The class also provides a [State] that can be used to determine the current
+ * state of the long press effect.
+ *
+ * @property[vibratorHelper] The [VibratorHelper] to deliver haptic effects.
+ * @property[effectDuration] The duration of the effect in ms.
+ */
+class QSLongPressEffect(
+ private val vibratorHelper: VibratorHelper?,
+ private val effectDuration: Int,
+) : View.OnTouchListener {
+
+ /** Current state */
+ var state = State.IDLE
+ @VisibleForTesting set
+
+ /** Flows for view control and action */
+ private val _effectProgress = MutableStateFlow<Float?>(null)
+ val effectProgress = _effectProgress.asStateFlow()
+
+ private val _actionType = MutableStateFlow<ActionType?>(null)
+ val actionType = _actionType.asStateFlow()
+
+ /** Haptic effects */
+ private val durations =
+ vibratorHelper?.getPrimitiveDurations(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+ VibrationEffect.Composition.PRIMITIVE_SPIN
+ )
+
+ private val longPressHint =
+ LongPressHapticBuilder.createLongPressHint(
+ durations?.get(0) ?: LongPressHapticBuilder.INVALID_DURATION,
+ durations?.get(1) ?: LongPressHapticBuilder.INVALID_DURATION,
+ effectDuration
+ )
+
+ private val snapEffect = LongPressHapticBuilder.createSnapEffect()
+
+ /* A coroutine scope and a timer job that waits for the pressedTimeout */
+ var scope: CoroutineScope? = null
+ private var waitJob: Job? = null
+
+ private val effectAnimator =
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = effectDuration.toLong()
+ interpolator = AccelerateDecelerateInterpolator()
+
+ doOnStart { handleAnimationStart() }
+ addUpdateListener { _effectProgress.value = animatedValue as Float }
+ doOnEnd { handleAnimationComplete() }
+ doOnCancel { handleAnimationCancel() }
+ }
+
+ private fun reverse() {
+ val pausedProgress = effectAnimator.animatedFraction
+ val effect =
+ LongPressHapticBuilder.createReversedEffect(
+ pausedProgress,
+ durations?.get(0) ?: 0,
+ effectDuration,
+ )
+ vibratorHelper?.cancel()
+ vibrate(effect)
+ effectAnimator.reverse()
+ }
+
+ private fun vibrate(effect: VibrationEffect?) {
+ if (vibratorHelper != null && effect != null) {
+ vibratorHelper.vibrate(effect)
+ }
+ }
+
+ /**
+ * Handle relevant touch events for the operation of a Tile.
+ *
+ * A click action is performed following the relevant logic that originates from the
+ * [MotionEvent.ACTION_UP] event depending on the current state.
+ */
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouch(view: View?, event: MotionEvent?): Boolean {
+ when (event?.actionMasked) {
+ MotionEvent.ACTION_DOWN -> handleActionDown()
+ MotionEvent.ACTION_UP -> handleActionUp()
+ MotionEvent.ACTION_CANCEL -> handleActionCancel()
+ }
+ return true
+ }
+
+ private fun handleActionDown() {
+ when (state) {
+ State.IDLE -> {
+ startPressedTimeoutWait()
+ state = State.TIMEOUT_WAIT
+ }
+ State.RUNNING_BACKWARDS -> effectAnimator.cancel()
+ else -> {}
+ }
+ }
+
+ private fun startPressedTimeoutWait() {
+ waitJob =
+ scope?.launch {
+ try {
+ delay(PRESSED_TIMEOUT)
+ handleTimeoutComplete()
+ } catch (_: CancellationException) {
+ state = State.IDLE
+ }
+ }
+ }
+
+ private fun handleActionUp() {
+ when (state) {
+ State.TIMEOUT_WAIT -> {
+ waitJob?.cancel()
+ _actionType.value = ActionType.CLICK
+ state = State.IDLE
+ }
+ State.RUNNING_FORWARD -> {
+ reverse()
+ state = State.RUNNING_BACKWARDS
+ }
+ else -> {}
+ }
+ }
+
+ private fun handleActionCancel() {
+ when (state) {
+ State.TIMEOUT_WAIT -> {
+ waitJob?.cancel()
+ state = State.IDLE
+ }
+ State.RUNNING_FORWARD -> {
+ reverse()
+ state = State.RUNNING_BACKWARDS
+ }
+ else -> {}
+ }
+ }
+
+ private fun handleAnimationStart() {
+ vibrate(longPressHint)
+ state = State.RUNNING_FORWARD
+ }
+
+ /** This function is called both when an animator completes or gets cancelled */
+ private fun handleAnimationComplete() {
+ if (state == State.RUNNING_FORWARD) {
+ vibrate(snapEffect)
+ _actionType.value = ActionType.LONG_PRESS
+ _effectProgress.value = null
+ }
+ if (state != State.TIMEOUT_WAIT) {
+ // This will happen if the animator did not finish by being cancelled
+ state = State.IDLE
+ }
+ }
+
+ private fun handleAnimationCancel() {
+ _effectProgress.value = 0f
+ startPressedTimeoutWait()
+ state = State.TIMEOUT_WAIT
+ }
+
+ private fun handleTimeoutComplete() {
+ if (state == State.TIMEOUT_WAIT && !effectAnimator.isRunning) {
+ effectAnimator.start()
+ }
+ }
+
+ fun clearActionType() {
+ _actionType.value = null
+ }
+
+ enum class State {
+ IDLE, /* The effect is idle waiting for touch input */
+ TIMEOUT_WAIT, /* The effect is waiting for a [PRESSED_TIMEOUT] period */
+ RUNNING_FORWARD, /* The effect is running normally */
+ RUNNING_BACKWARDS, /* The effect was interrupted and is now running backwards */
+ }
+
+ /* A type of action to perform on the view depending on the effect's state and logic */
+ enum class ActionType {
+ CLICK,
+ LONG_PRESS,
+ }
+
+ companion object {
+ /**
+ * A timeout to let the tile resolve if it is being swiped/scrolled. Since QS tiles are
+ * inside a scrollable container, they will be considered pressed only after a tap timeout.
+ */
+ val PRESSED_TIMEOUT = ViewConfiguration.getTapTimeout().toLong() + 20L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
new file mode 100644
index 0000000..e298154
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.qs
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.qs.tileimpl.QSTileViewImpl
+import kotlinx.coroutines.launch
+
+object QSLongPressEffectViewBinder {
+
+ fun bind(
+ tile: QSTileViewImpl,
+ effect: QSLongPressEffect?,
+ ) {
+ if (effect == null) return
+
+ tile.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ effect.scope = this
+
+ launch {
+ effect.effectProgress.collect { progress ->
+ progress?.let {
+ if (it == 0f) {
+ tile.bringToFront()
+ }
+ tile.updateLongPressEffectProperties(it)
+ }
+ }
+ }
+
+ launch {
+ effect.actionType.collect { action ->
+ action?.let {
+ when (it) {
+ QSLongPressEffect.ActionType.CLICK -> tile.performClick()
+ QSLongPressEffect.ActionType.LONG_PRESS -> tile.performLongClick()
+ }
+ effect.clearActionType()
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 106fdf1..5565ee2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -42,6 +42,7 @@
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder
@@ -107,6 +108,7 @@
private val lockscreenContentViewModel: LockscreenContentViewModel,
private val lockscreenSceneBlueprintsLazy: Lazy<Set<LockscreenSceneBlueprint>>,
private val keyguardBlueprintViewBinder: KeyguardBlueprintViewBinder,
+ private val clockInteractor: KeyguardClockInteractor,
) : CoreStartable {
private var rootViewHandle: DisposableHandle? = null
@@ -220,7 +222,11 @@
blueprints.mapNotNull { it as? ComposableLockscreenSceneBlueprint }.toSet()
return ComposeView(context).apply {
setContent {
- LockscreenContent(viewModel = viewModel, blueprints = sceneBlueprints)
+ LockscreenContent(
+ viewModel = viewModel,
+ blueprints = sceneBlueprints,
+ clockInteractor = clockInteractor
+ )
.Content(modifier = Modifier.fillMaxSize())
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index a37397d..43a8b40 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -140,13 +140,13 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.ui.viewmodel.DreamViewModel;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.SystemPropertiesHelper;
import com.android.systemui.keyguard.dagger.KeyguardModule;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.shared.model.TransitionStep;
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -1229,9 +1229,7 @@
if (isDream) {
initAlphaForAnimationTargets(wallpapers);
- getRemoteSurfaceAlphaApplier().accept(0.0f);
- mDreamingToLockscreenTransitionViewModel.get()
- .startTransition();
+ mDreamViewModel.get().startTransitionFromDream();
mUnoccludeFromDreamFinishedCallback = finishedCallback;
return;
}
@@ -1359,8 +1357,7 @@
private final UiEventLogger mUiEventLogger;
private final SessionTracker mSessionTracker;
private final CoroutineDispatcher mMainDispatcher;
- private final Lazy<DreamingToLockscreenTransitionViewModel>
- mDreamingToLockscreenTransitionViewModel;
+ private final Lazy<DreamViewModel> mDreamViewModel;
private RemoteAnimationTarget mRemoteAnimationTarget;
private final Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
@@ -1409,7 +1406,7 @@
SystemSettings systemSettings,
SystemClock systemClock,
@Main CoroutineDispatcher mainDispatcher,
- Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
+ Lazy<DreamViewModel> dreamViewModel,
SystemPropertiesHelper systemPropertiesHelper,
Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
SelectedUserInteractor selectedUserInteractor,
@@ -1480,7 +1477,7 @@
mUiEventLogger = uiEventLogger;
mSessionTracker = sessionTracker;
- mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel;
+ mDreamViewModel = dreamViewModel;
mWmLockscreenVisibilityManager = wmLockscreenVisibilityManager;
mMainDispatcher = mainDispatcher;
@@ -1611,9 +1608,8 @@
ViewRootImpl viewRootImpl = mKeyguardViewControllerLazy.get().getViewRootImpl();
if (viewRootImpl != null) {
- DreamingToLockscreenTransitionViewModel viewModel =
- mDreamingToLockscreenTransitionViewModel.get();
- collectFlow(viewRootImpl.getView(), viewModel.getDreamOverlayAlpha(),
+ final DreamViewModel viewModel = mDreamViewModel.get();
+ collectFlow(viewRootImpl.getView(), viewModel.getDreamAlpha(),
getRemoteSurfaceAlphaApplier(), mMainDispatcher);
collectFlow(viewRootImpl.getView(), viewModel.getTransitionEnded(),
getFinishedCallbackConsumer(), mMainDispatcher);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 5306645..a243b8e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -43,6 +43,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.ui.viewmodel.DreamViewModel;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.SystemPropertiesHelper;
@@ -59,7 +60,6 @@
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger;
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLoggerImpl;
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransitionModule;
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.settings.UserTracker;
@@ -160,7 +160,7 @@
SystemSettings systemSettings,
SystemClock systemClock,
@Main CoroutineDispatcher mainDispatcher,
- Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
+ Lazy<DreamViewModel> dreamViewModel,
SystemPropertiesHelper systemPropertiesHelper,
Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
SelectedUserInteractor selectedUserInteractor,
@@ -207,7 +207,7 @@
systemSettings,
systemClock,
mainDispatcher,
- dreamingToLockscreenTransitionViewModel,
+ dreamViewModel,
systemPropertiesHelper,
wmLockscreenVisibilityManager,
selectedUserInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index f5f5571..882f231 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -175,7 +175,6 @@
pw.println("isFingerprintAuthCurrentlyAllowed=${isFingerprintAuthCurrentlyAllowed.value}")
pw.println("isNonStrongBiometricAllowed=${isNonStrongBiometricAllowed.value}")
pw.println("isStrongBiometricAllowed=${isStrongBiometricAllowed.value}")
- pw.println("isFingerprintEnabledByDevicePolicy=${isFingerprintEnabledByDevicePolicy.value}")
}
/** UserId of the current selected user. */
@@ -324,22 +323,14 @@
else isNonStrongBiometricAllowed
}
- private val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> =
- selectedUserId
- .flatMapLatest { userId ->
- devicePolicyChangedForAllUsers
- .transformLatest { emit(devicePolicyManager.isFingerprintDisabled(userId)) }
- .flowOn(backgroundDispatcher)
- .distinctUntilChanged()
- }
- .stateIn(
- scope,
- started = SharingStarted.Eagerly,
- initialValue =
- devicePolicyManager.isFingerprintDisabled(
- userRepository.getSelectedUserInfo().id
- )
- )
+ private val isFingerprintEnabledByDevicePolicy: Flow<Boolean> =
+ selectedUserId.flatMapLatest { userId ->
+ devicePolicyChangedForAllUsers
+ .transformLatest { emit(devicePolicyManager.isFingerprintDisabled(userId)) }
+ .onStart { emit(devicePolicyManager.isFingerprintDisabled(userId)) }
+ .flowOn(backgroundDispatcher)
+ .distinctUntilChanged()
+ }
override val isFingerprintEnrolledAndEnabled: StateFlow<Boolean> =
isFingerprintEnrolled
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
index 0659c7c..a49b3ae 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
@@ -19,7 +19,6 @@
import android.os.Handler
import android.util.Log
-import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
@@ -30,7 +29,6 @@
import java.io.PrintWriter
import java.util.TreeMap
import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -49,7 +47,6 @@
class KeyguardBlueprintRepository
@Inject
constructor(
- configurationRepository: ConfigurationRepository,
blueprints: Set<@JvmSuppressWildcards KeyguardBlueprint>,
@Main val handler: Handler,
val assert: ThreadAssert,
@@ -60,7 +57,6 @@
TreeMap<String, KeyguardBlueprint>().apply { putAll(blueprints.associateBy { it.id }) }
val blueprint: MutableStateFlow<KeyguardBlueprint> = MutableStateFlow(blueprintIdMap[DEFAULT]!!)
val refreshTransition = MutableSharedFlow<Config>(extraBufferCapacity = 1)
- val configurationChange: Flow<Unit> = configurationRepository.onAnyConfigurationChange
private var targetTransitionConfig: Config? = null
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 3a6423d..1298fa5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -24,7 +24,6 @@
import com.android.systemui.biometrics.data.repository.FacePropertyRepository
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.common.shared.model.Position
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
@@ -80,12 +79,6 @@
val keyguardAlpha: StateFlow<Float>
/**
- * Observable of the relative offset of the lock-screen clock from its natural position on the
- * screen.
- */
- val clockPosition: StateFlow<Position>
-
- /**
* Observable for whether the keyguard is showing.
*
* Note: this is also `true` when the lock-screen is occluded with an `Activity` "above" it in
@@ -241,11 +234,6 @@
fun setKeyguardAlpha(alpha: Float)
/**
- * Sets the relative offset of the lock-screen clock from its natural position on the screen.
- */
- fun setClockPosition(x: Int, y: Int)
-
- /**
* Returns whether the keyguard bottom area should be constrained to the top of the lock icon
*/
fun isUdfpsSupported(): Boolean
@@ -324,9 +312,6 @@
private val _keyguardAlpha = MutableStateFlow(1f)
override val keyguardAlpha = _keyguardAlpha.asStateFlow()
- private val _clockPosition = MutableStateFlow(Position(0, 0))
- override val clockPosition = _clockPosition.asStateFlow()
-
private val _clockShouldBeCentered = MutableStateFlow(true)
override val clockShouldBeCentered: Flow<Boolean> = _clockShouldBeCentered.asStateFlow()
@@ -678,10 +663,6 @@
_keyguardAlpha.value = alpha
}
- override fun setClockPosition(x: Int, y: Int) {
- _clockPosition.value = Position(x, y)
- }
-
override fun isUdfpsSupported(): Boolean = keyguardUpdateMonitor.isUdfpsSupported
override fun setQuickSettingsVisible(isVisible: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
index d9479de..eac476f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
@@ -26,7 +26,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
-import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.data.repository.PowerRepository
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakeSleepReason.TAP
import com.android.systemui.res.R
@@ -47,6 +47,7 @@
import kotlinx.coroutines.flow.map
val DEFAULT_REVEAL_EFFECT = LiftReveal
+const val DEFAULT_REVEAL_DURATION = 500L
/**
* Encapsulates state relevant to the light reveal scrim, the view used to reveal/hide screen
@@ -63,7 +64,9 @@
val revealAmount: Flow<Float>
- fun startRevealAmountAnimator(reveal: Boolean)
+ val isAnimating: Boolean
+
+ fun startRevealAmountAnimator(reveal: Boolean, duration: Long = DEFAULT_REVEAL_DURATION)
}
@SysUISingleton
@@ -72,7 +75,7 @@
constructor(
keyguardRepository: KeyguardRepository,
val context: Context,
- powerInteractor: PowerInteractor,
+ powerRepository: PowerRepository,
private val scrimLogger: ScrimLogger,
) : LightRevealScrimRepository {
companion object {
@@ -125,7 +128,7 @@
/** The reveal effect we'll use for the next non-biometric unlock (tap, power button, etc). */
private val nonBiometricRevealEffect: Flow<LightRevealEffect?> =
- powerInteractor.detailedWakefulness.flatMapLatest { wakefulnessModel ->
+ powerRepository.wakefulness.flatMapLatest { wakefulnessModel ->
when {
wakefulnessModel.isAwakeOrAsleepFrom(WakeSleepReason.POWER_BUTTON) ->
powerButtonRevealEffect
@@ -134,7 +137,7 @@
}
}
- private val revealAmountAnimator = ValueAnimator.ofFloat(0f, 1f).apply { duration = 500 }
+ private val revealAmountAnimator = ValueAnimator.ofFloat(0f, 1f)
override val revealAmount: Flow<Float> = callbackFlow {
val updateListener =
@@ -149,18 +152,21 @@
revealAmountAnimator.addUpdateListener(updateListener)
awaitClose { revealAmountAnimator.removeUpdateListener(updateListener) }
}
+ override val isAnimating: Boolean
+ get() = revealAmountAnimator.isRunning
private var willBeOrIsRevealed: Boolean? = null
- override fun startRevealAmountAnimator(reveal: Boolean) {
+ override fun startRevealAmountAnimator(reveal: Boolean, duration: Long) {
if (reveal == willBeOrIsRevealed) return
willBeOrIsRevealed = reveal
+ revealAmountAnimator.duration = duration
if (reveal && !revealAmountAnimator.isRunning) {
revealAmountAnimator.start()
} else {
revealAmountAnimator.reverse()
}
- scrimLogger.d(TAG, "startRevealAmountAnimator, reveal: ", reveal)
+ scrimLogger.d(TAG, "startRevealAmountAnimator, reveal", reveal)
}
override val revealEffect =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
index 7ae70a9..ca86289 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
@@ -19,7 +19,7 @@
import android.content.Context
import androidx.annotation.DimenRes
-import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.doze.util.BurnInHelperWrapper
@@ -47,13 +47,15 @@
private val context: Context,
private val burnInHelperWrapper: BurnInHelperWrapper,
@Application private val scope: CoroutineScope,
- private val configurationRepository: ConfigurationRepository,
+ private val configurationInteractor: ConfigurationInteractor,
private val keyguardInteractor: KeyguardInteractor,
) {
val deviceEntryIconXOffset: StateFlow<Int> =
burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_x, isXAxis = true)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
val deviceEntryIconYOffset: StateFlow<Int> =
burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_y, isXAxis = false)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
val udfpsProgress: StateFlow<Float> =
keyguardInteractor.dozeTimeTick
.mapLatest { burnInHelperWrapper.burnInProgressOffset() }
@@ -63,18 +65,18 @@
burnInHelperWrapper.burnInProgressOffset()
)
- val keyguardBurnIn: Flow<BurnInModel> =
- combine(
- burnInOffset(R.dimen.burn_in_prevention_offset_x, isXAxis = true),
- burnInOffset(R.dimen.burn_in_prevention_offset_y, isXAxis = false).map {
- it * 2 -
- context.resources.getDimensionPixelSize(R.dimen.burn_in_prevention_offset_y)
+ /** Given the max x,y dimens, determine the current translation shifts. */
+ fun burnIn(xDimenResourceId: Int, yDimenResourceId: Int): Flow<BurnInModel> {
+ return combine(
+ burnInOffset(xDimenResourceId, isXAxis = true),
+ burnInOffset(yDimenResourceId, isXAxis = false).map {
+ it * 2 - context.resources.getDimensionPixelSize(yDimenResourceId)
}
) { translationX, translationY ->
BurnInModel(translationX, translationY, burnInHelperWrapper.burnInScale())
}
.distinctUntilChanged()
- .stateIn(scope, SharingStarted.Lazily, BurnInModel())
+ }
/**
* Use for max burn-in offsets that are NOT specified in pixels. This flow will recalculate the
@@ -84,23 +86,14 @@
private fun burnInOffset(
@DimenRes maxBurnInOffsetResourceId: Int,
isXAxis: Boolean,
- ): StateFlow<Int> {
- return configurationRepository.onAnyConfigurationChange
- .flatMapLatest {
- val maxBurnInOffsetPixels =
- context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId)
- keyguardInteractor.dozeTimeTick.mapLatest {
- calculateOffset(maxBurnInOffsetPixels, isXAxis)
- }
+ ): Flow<Int> {
+ return configurationInteractor.onAnyConfigurationChange.flatMapLatest {
+ val maxBurnInOffsetPixels =
+ context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId)
+ keyguardInteractor.dozeTimeTick.mapLatest {
+ calculateOffset(maxBurnInOffsetPixels, isXAxis)
}
- .stateIn(
- scope,
- SharingStarted.Lazily,
- calculateOffset(
- context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId),
- isXAxis,
- )
- )
+ }
}
/**
@@ -111,24 +104,14 @@
private fun burnInOffsetDefinedInPixels(
@DimenRes maxBurnInOffsetResourceId: Int,
isXAxis: Boolean,
- ): StateFlow<Int> {
- return configurationRepository.scaleForResolution
- .flatMapLatest { scale ->
- val maxBurnInOffsetPixels =
- context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId)
- keyguardInteractor.dozeTimeTick.mapLatest {
- calculateOffset(maxBurnInOffsetPixels, isXAxis, scale)
- }
+ ): Flow<Int> {
+ return configurationInteractor.scaleForResolution.flatMapLatest { scale ->
+ val maxBurnInOffsetPixels =
+ context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId)
+ keyguardInteractor.dozeTimeTick.mapLatest {
+ calculateOffset(maxBurnInOffsetPixels, isXAxis, scale)
}
- .stateIn(
- scope,
- SharingStarted.WhileSubscribed(),
- calculateOffset(
- context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId),
- isXAxis,
- configurationRepository.getResolutionScale(),
- )
- )
+ }
}
private fun calculateOffset(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 3137138..9a6088d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -35,6 +35,7 @@
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
@@ -97,6 +98,18 @@
}
}
+ fun startToGlanceableHubTransition() {
+ scope.launch {
+ KeyguardWmStateRefactor.isUnexpectedlyInLegacyMode()
+ if (
+ transitionInteractor.startedKeyguardState.replayCache.last() ==
+ KeyguardState.DREAMING
+ ) {
+ startTransitionTo(KeyguardState.GLANCEABLE_HUB)
+ }
+ }
+ }
+
private fun listenForDreamingToOccluded() {
if (KeyguardWmStateRefactor.isEnabled) {
scope.launch {
@@ -205,14 +218,18 @@
return ValueAnimator().apply {
interpolator = Interpolators.LINEAR
duration =
- if (toState == KeyguardState.LOCKSCREEN) TO_LOCKSCREEN_DURATION.inWholeMilliseconds
- else DEFAULT_DURATION.inWholeMilliseconds
+ when (toState) {
+ KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
+ KeyguardState.GLANCEABLE_HUB -> TO_GLANCEABLE_HUB_DURATION
+ else -> DEFAULT_DURATION
+ }.inWholeMilliseconds
}
}
companion object {
const val TAG = "FromDreamingTransitionInteractor"
private val DEFAULT_DURATION = 500.milliseconds
+ val TO_GLANCEABLE_HUB_DURATION = 1.seconds
val TO_LOCKSCREEN_DURATION = 1167.milliseconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
index 7443010..197221a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
@@ -21,7 +21,6 @@
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalTransitionProgress
import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
@@ -29,13 +28,10 @@
import com.android.systemui.util.kotlin.sample
import java.util.UUID
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.flow.flowOn
class GlanceableHubTransitions
@Inject
constructor(
- @Background private val bgDispatcher: CoroutineDispatcher,
private val transitionInteractor: KeyguardTransitionInteractor,
private val transitionRepository: KeyguardTransitionRepository,
private val communalInteractor: CommunalInteractor,
@@ -64,16 +60,16 @@
communalInteractor
.transitionProgressToScene(toScene)
.sample(
- transitionInteractor.startedKeyguardTransitionStep.flowOn(bgDispatcher),
+ transitionInteractor.startedKeyguardState,
::Pair,
)
- .collect { (transitionProgress, lastStartedStep) ->
+ .collect { (transitionProgress, lastStartedState) ->
val id = transitionId
if (id == null) {
// No transition started.
if (
transitionProgress is CommunalTransitionProgress.Transition &&
- lastStartedStep.to == fromState
+ lastStartedState == fromState
) {
transitionId =
transitionRepository.startTransition(
@@ -86,7 +82,7 @@
)
}
} else {
- if (lastStartedStep.to != toState) {
+ if (lastStartedState != toState) {
return@collect
}
// An existing `id` means a transition is started, and calls to
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
index bc3f0cc..c877192 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
@@ -20,6 +20,8 @@
package com.android.systemui.keyguard.domain.interactor
import android.content.Context
+import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository
@@ -34,7 +36,9 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
@@ -47,6 +51,8 @@
private val context: Context,
private val splitShadeStateController: SplitShadeStateController,
private val clockInteractor: KeyguardClockInteractor,
+ configurationInteractor: ConfigurationInteractor,
+ fingerprintPropertyInteractor: FingerprintPropertyInteractor,
) {
/** The current blueprint for the lockscreen. */
@@ -58,11 +64,14 @@
*/
val refreshTransition = keyguardBlueprintRepository.refreshTransition
+ private val configOrPropertyChange =
+ merge(
+ configurationInteractor.onAnyConfigurationChange,
+ fingerprintPropertyInteractor.propertiesInitialized.filter { it }.map {}, // map to Unit
+ )
init {
applicationScope.launch {
- keyguardBlueprintRepository.configurationChange
- .onStart { emit(Unit) }
- .collect { updateBlueprint() }
+ configOrPropertyChange.onStart { emit(Unit) }.collect { updateBlueprint() }
}
applicationScope.launch { clockInteractor.currentClock.collect { updateBlueprint() } }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
index d2a7486..b9ec58c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
@@ -22,6 +22,8 @@
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
/** Encapsulates business-logic specifically related to the keyguard bottom area. */
@SysUISingleton
@@ -35,10 +37,13 @@
/** The amount of alpha for the UI components of the bottom area. */
val alpha: Flow<Float> = repository.bottomAreaAlpha
/** The position of the keyguard clock. */
- val clockPosition: Flow<Position> = repository.clockPosition
+ private val _clockPosition = MutableStateFlow(Position(0, 0))
+ /** See [ClockSection] */
+ @Deprecated("with migrateClocksToBlueprint()")
+ val clockPosition: Flow<Position> = _clockPosition.asStateFlow()
fun setClockPosition(x: Int, y: Int) {
- repository.setClockPosition(x, y)
+ _clockPosition.value = Position(x, y)
}
fun setAlpha(alpha: Float) {
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 f321bd7..143edf9 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,7 +27,6 @@
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.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardRepository
@@ -37,6 +36,7 @@
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -235,9 +235,6 @@
/** The approximate location on the screen of the face unlock sensor, if one is available. */
val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation
- /** The position of the keyguard clock. */
- val clockPosition: Flow<Position> = repository.clockPosition
-
@Deprecated("Use the relevant TransitionViewModel")
val keyguardAlpha: Flow<Float> = repository.keyguardAlpha
@@ -272,8 +269,11 @@
configurationInteractor
.dimensionPixelSize(R.dimen.keyguard_translate_distance_on_swipe_up)
.flatMapLatest { translationDistance ->
- shadeRepository.legacyShadeExpansion.map {
- if (it == 0f) {
+ combine(
+ shadeRepository.legacyShadeExpansion.onStart { emit(0f) },
+ keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) },
+ ) { legacyShadeExpansion, goneValue ->
+ if (goneValue == 1f || legacyShadeExpansion == 0f) {
// Reset the translation value
0f
} else {
@@ -281,11 +281,12 @@
MathUtils.lerp(
translationDistance,
0,
- Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(it)
+ Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(legacyShadeExpansion)
)
}
}
}
+ .distinctUntilChanged()
val clockShouldBeCentered: Flow<Boolean> = repository.clockShouldBeCentered
@@ -345,10 +346,6 @@
repository.setQuickSettingsVisible(isVisible)
}
- fun setClockPosition(x: Int, y: Int) {
- repository.setClockPosition(x, y)
- }
-
fun setAlpha(alpha: Float) {
repository.setKeyguardAlpha(alpha)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 8905c9e..2d944c6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -25,14 +25,13 @@
import com.android.systemui.power.shared.model.ScreenPowerState
import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.util.kotlin.sample
+import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
-@ExperimentalCoroutinesApi
@SysUISingleton
class LightRevealScrimInteractor
@Inject
@@ -41,7 +40,7 @@
private val lightRevealScrimRepository: LightRevealScrimRepository,
@Application private val scope: CoroutineScope,
private val scrimLogger: ScrimLogger,
- private val powerInteractor: PowerInteractor,
+ private val powerInteractor: Lazy<PowerInteractor>,
) {
init {
listenForStartedKeyguardTransitionStep()
@@ -81,31 +80,33 @@
}
private fun screenIsShowingContent() =
- powerInteractor.screenPowerState.value != ScreenPowerState.SCREEN_OFF &&
- powerInteractor.screenPowerState.value != ScreenPowerState.SCREEN_TURNING_ON
+ powerInteractor.get().screenPowerState.value != ScreenPowerState.SCREEN_OFF &&
+ powerInteractor.get().screenPowerState.value != ScreenPowerState.SCREEN_TURNING_ON
+
+ val isAnimating: Boolean
+ get() = lightRevealScrimRepository.isAnimating
+
+ /**
+ * Whether the light reveal scrim will be fully revealed (revealAmount = 1.0f) in the given
+ * state after the transition is complete. If false, scrim will be fully hidden.
+ */
+ private fun willBeRevealedInState(state: KeyguardState): Boolean {
+ return when (state) {
+ KeyguardState.OFF -> false
+ KeyguardState.DOZING -> false
+ KeyguardState.AOD -> false
+ KeyguardState.DREAMING -> true
+ KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> true
+ KeyguardState.GLANCEABLE_HUB -> true
+ KeyguardState.ALTERNATE_BOUNCER -> true
+ KeyguardState.PRIMARY_BOUNCER -> true
+ KeyguardState.LOCKSCREEN -> true
+ KeyguardState.GONE -> true
+ KeyguardState.OCCLUDED -> true
+ }
+ }
companion object {
-
- /**
- * Whether the light reveal scrim will be fully revealed (revealAmount = 1.0f) in the given
- * state after the transition is complete. If false, scrim will be fully hidden.
- */
- private fun willBeRevealedInState(state: KeyguardState): Boolean {
- return when (state) {
- KeyguardState.OFF -> false
- KeyguardState.DOZING -> false
- KeyguardState.AOD -> false
- KeyguardState.DREAMING -> true
- KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> true
- KeyguardState.GLANCEABLE_HUB -> true
- KeyguardState.ALTERNATE_BOUNCER -> true
- KeyguardState.PRIMARY_BOUNCER -> true
- KeyguardState.LOCKSCREEN -> true
- KeyguardState.GONE -> true
- KeyguardState.OCCLUDED -> true
- }
- }
-
val TAG = LightRevealScrimInteractor::class.simpleName!!
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TrustInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TrustInteractor.kt
new file mode 100644
index 0000000..2ff6e16
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TrustInteractor.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.TrustRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+
+/** Encapsulates any state relevant to trust agents and trust grants. */
+@SysUISingleton
+class TrustInteractor @Inject constructor(repository: TrustRepository) {
+ /**
+ * Whether the current user has a trust agent enabled. This is true if the user has at least one
+ * trust agent enabled in settings.
+ */
+ val isEnrolledAndEnabled: StateFlow<Boolean> = repository.isCurrentUserTrustUsuallyManaged
+
+ /**
+ * Whether the current user's trust agent is currently allowed, this will be false if trust
+ * agent is disabled for any reason (security timeout, disabled on lock screen by opening the
+ * power menu, etc), it does not include temporary biometric lockouts.
+ */
+ val isTrustAgentCurrentlyAllowed: StateFlow<Boolean> = repository.isCurrentUserTrustManaged
+
+ /** Whether the current user is trusted by any of the enabled trust agents. */
+ val isTrusted: Flow<Boolean> = repository.isCurrentUserTrusted
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt
index 08904b6..d6f3634 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt
@@ -32,6 +32,9 @@
val isPrimaryAuthRequiredAfterTimeout =
containsFlag(flag, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT)
+ val isPrimaryAuthRequiredAfterLockout =
+ containsFlag(flag, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT)
+
val isPrimaryAuthRequiredAfterDpmLockdown =
containsFlag(
flag,
@@ -47,7 +50,7 @@
LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED
)
- val primaryAuthRequiredForUnattendedUpdate =
+ val isPrimaryAuthRequiredForUnattendedUpdate =
containsFlag(
flag,
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
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 dc1f33d..fc95ec9 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
@@ -73,7 +73,6 @@
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@@ -148,6 +147,7 @@
viewModel.alpha(viewState).collect { alpha ->
view.alpha = alpha
childViews[statusViewId]?.alpha = alpha
+ childViews[burnInLayerId]?.alpha = alpha
}
}
}
@@ -195,66 +195,68 @@
// large clock isn't added to burnInLayer due to its scale transition
// so we also need to add translation to it here
// same as translationX
- burnInParams
- .flatMapLatest { params -> viewModel.translationY(params) }
- .collect { y ->
- childViews[burnInLayerId]?.translationY = y
- childViews[largeClockId]?.translationY = y
- childViews[aodNotificationIconContainerId]?.translationY = y
- }
+ viewModel.translationY.collect { y ->
+ childViews[burnInLayerId]?.translationY = y
+ childViews[largeClockId]?.translationY = y
+ childViews[aodNotificationIconContainerId]?.translationY = y
+ }
}
launch {
- burnInParams
- .flatMapLatest { params -> viewModel.translationX(params) }
- .collect { state ->
- val px = state.value ?: return@collect
- when {
- state.isToOrFrom(KeyguardState.AOD) -> {
- childViews[largeClockId]?.translationX = px
- childViews[burnInLayerId]?.translationX = px
- childViews[aodNotificationIconContainerId]
- ?.translationX = px
- }
- state.isToOrFrom(KeyguardState.GLANCEABLE_HUB) -> {
- for ((key, childView) in childViews.entries) {
- when (key) {
- indicationArea,
- startButton,
- endButton,
- lockIcon -> {
- // Do not move these views
- }
- else -> childView.translationX = px
+ viewModel.translationX.collect { state ->
+ val px = state.value ?: return@collect
+ when {
+ state.isToOrFrom(KeyguardState.AOD) -> {
+ childViews[largeClockId]?.translationX = px
+ childViews[burnInLayerId]?.translationX = px
+ childViews[aodNotificationIconContainerId]?.translationX =
+ px
+ }
+ state.isToOrFrom(KeyguardState.GLANCEABLE_HUB) -> {
+ for ((key, childView) in childViews.entries) {
+ when (key) {
+ indicationArea,
+ startButton,
+ endButton,
+ lockIcon -> {
+ // Do not move these views
}
+ else -> childView.translationX = px
}
}
}
}
+ }
}
launch {
- burnInParams
- .flatMapLatest { params -> viewModel.scale(params) }
- .collect { scaleViewModel ->
- if (scaleViewModel.scaleClockOnly) {
- // For clocks except weather clock, we have scale transition
- // besides translate
- childViews[largeClockId]?.let {
- it.scaleX = scaleViewModel.scale
- it.scaleY = scaleViewModel.scale
- }
- } else {
- // For weather clock, large clock should have only scale
- // transition with other parts in burnInLayer
- childViews[burnInLayerId]?.scaleX = scaleViewModel.scale
- childViews[burnInLayerId]?.scaleY = scaleViewModel.scale
- childViews[aodNotificationIconContainerId]?.scaleX =
- scaleViewModel.scale
- childViews[aodNotificationIconContainerId]?.scaleY =
- scaleViewModel.scale
+ viewModel.scale.collect { scaleViewModel ->
+ if (scaleViewModel.scaleClockOnly) {
+ // For clocks except weather clock, we have scale transition
+ // besides translate
+ childViews[largeClockId]?.let {
+ it.scaleX = scaleViewModel.scale
+ it.scaleY = scaleViewModel.scale
}
+ // Make sure to reset these views, or they will be invisible
+ if (childViews[burnInLayerId]?.scaleX != 1f) {
+ childViews[burnInLayerId]?.scaleX = 1f
+ childViews[burnInLayerId]?.scaleY = 1f
+ childViews[aodNotificationIconContainerId]?.scaleX = 1f
+ childViews[aodNotificationIconContainerId]?.scaleY = 1f
+ view.requestLayout()
+ }
+ } else {
+ // For weather clock, large clock should have only scale
+ // transition with other parts in burnInLayer
+ childViews[burnInLayerId]?.scaleX = scaleViewModel.scale
+ childViews[burnInLayerId]?.scaleY = scaleViewModel.scale
+ childViews[aodNotificationIconContainerId]?.scaleX =
+ scaleViewModel.scale
+ childViews[aodNotificationIconContainerId]?.scaleY =
+ scaleViewModel.scale
}
+ }
}
if (NotificationIconContainerRefactor.isEnabled) {
@@ -311,6 +313,8 @@
}
}
+ launch { burnInParams.collect { viewModel.updateBurnInParams(it) } }
+
if (deviceEntryHapticsInteractor != null && vibratorHelper != null) {
launch {
deviceEntryHapticsInteractor.playSuccessHaptic.collect {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
index 98bebd0..88ce9dc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
@@ -21,6 +21,8 @@
import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.view.KeyguardRootView
@@ -37,24 +39,24 @@
private val clockViewModel: KeyguardClockViewModel,
) : KeyguardSection() {
private lateinit var burnInLayer: AodBurnInLayer
+ // The burn-in layer requires at least 1 view at all times
+ private val emptyView: View by lazy {
+ View(context, null).apply {
+ id = R.id.burn_in_layer_empty_view
+ visibility = View.GONE
+ }
+ }
override fun addViews(constraintLayout: ConstraintLayout) {
if (!migrateClocksToBlueprint()) {
return
}
- // The burn-in layer requires at least 1 view at all times
- val emptyView = View(context, null).apply { id = View.generateViewId() }
constraintLayout.addView(emptyView)
burnInLayer =
AodBurnInLayer(context).apply {
id = R.id.burn_in_layer
registerListener(rootView)
addView(emptyView)
- if (!migrateClocksToBlueprint()) {
- val statusView =
- constraintLayout.requireViewById<View>(R.id.keyguard_status_view)
- addView(statusView)
- }
}
constraintLayout.addView(burnInLayer)
}
@@ -70,6 +72,13 @@
if (!migrateClocksToBlueprint()) {
return
}
+
+ constraintSet.apply {
+ // The empty view should not occupy any space
+ constrainHeight(R.id.burn_in_layer_empty_view, 1)
+ constrainWidth(R.id.burn_in_layer_empty_view, 0)
+ connect(R.id.burn_in_layer_empty_view, BOTTOM, PARENT_ID, BOTTOM)
+ }
}
override fun removeViews(constraintLayout: ConstraintLayout) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index 3fc9b42..1085f94 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
@@ -26,7 +26,6 @@
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
-import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.LockIconView
import com.android.keyguard.LockIconViewController
import com.android.systemui.Flags.keyguardBottomAreaRefactor
@@ -56,7 +55,6 @@
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val authController: AuthController,
private val windowManager: WindowManager,
private val context: Context,
@@ -111,7 +109,12 @@
}
override fun applyConstraints(constraintSet: ConstraintSet) {
- val isUdfpsSupported = keyguardUpdateMonitor.isUdfpsSupported
+ val isUdfpsSupported =
+ if (DeviceEntryUdfpsRefactor.isEnabled) {
+ deviceEntryIconViewModel.get().isUdfpsSupported.value
+ } else {
+ authController.isUdfpsSupported
+ }
val scaleFactor: Float = authController.scaleFactor
val mBottomPaddingPx =
context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModel.kt
index 9edb4d1..bbe5fed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModel.kt
@@ -23,13 +23,19 @@
import com.android.systemui.deviceentry.shared.model.FaceTimeoutMessage
import com.android.systemui.deviceentry.shared.model.FingerprintLockoutMessage
import com.android.systemui.deviceentry.shared.model.FingerprintMessage
+import com.android.systemui.statusbar.KeyguardIndicationController.DEFAULT_MESSAGE_TIME
+import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
/** View model for the alternate bouncer message area. */
@ExperimentalCoroutinesApi
@@ -38,19 +44,32 @@
constructor(
biometricMessageInteractor: BiometricMessageInteractor,
alternateBouncerInteractor: AlternateBouncerInteractor,
+ systemClock: com.android.systemui.util.time.SystemClock,
) {
-
- private val faceMessage: Flow<FaceMessage> =
- biometricMessageInteractor.faceMessage.filterNot { it is FaceTimeoutMessage }
+ private val fingerprintMessageWithTimestamp: Flow<Pair<FingerprintMessage?, Long>> =
+ biometricMessageInteractor.fingerprintMessage
+ .filterNot { it is FingerprintLockoutMessage }
+ .map { Pair(it, systemClock.uptimeMillis()) }
+ .filterIsInstance<Pair<FingerprintMessage?, Long>>()
+ .onStart { emit(Pair(null, -3500L)) }
private val fingerprintMessage: Flow<FingerprintMessage> =
- biometricMessageInteractor.fingerprintMessage.filterNot { it is FingerprintLockoutMessage }
+ fingerprintMessageWithTimestamp.filter { it.first != null }.map { it.first!! }
+ private val faceMessage: Flow<FaceMessage> =
+ biometricMessageInteractor.faceMessage
+ .filterNot { it is FaceTimeoutMessage }
+ // Don't show face messages if within the default message time for fp messages to show
+ .sample(fingerprintMessageWithTimestamp, ::Pair)
+ .filter { (_, fpMessage) ->
+ (systemClock.uptimeMillis() - fpMessage.second) >= DEFAULT_MESSAGE_TIME
+ }
+ .map { (faceMsg, _) -> faceMsg }
val message: Flow<BiometricMessage?> =
alternateBouncerInteractor.isVisible.flatMapLatest { isVisible ->
if (isVisible) {
merge(
- faceMessage,
fingerprintMessage,
+ faceMessage,
)
} else {
flowOf(null)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
index 7be390a..f961e08 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.util.Log
import android.util.MathUtils
import com.android.app.animation.Interpolators
import com.android.keyguard.KeyguardClockSwitch
@@ -62,23 +63,26 @@
private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
private val keyguardClockViewModel: KeyguardClockViewModel,
) {
- /** Horizontal translation for elements that need to apply anti-burn-in tactics. */
- fun translationX(
- params: BurnInParameters,
- ): Flow<Float> {
- return burnIn(params).map { it.translationX.toFloat() }
- }
+ private val TAG = "AodBurnInViewModel"
- /** Vertical translation for elements that need to apply anti-burn-in tactics. */
- fun translationY(
- params: BurnInParameters,
- ): Flow<Float> {
+ /** All burn-in movement: x,y,scale, to shift items and prevent burn-in */
+ fun movement(
+ burnInParams: BurnInParameters,
+ ): Flow<BurnInModel> {
+ val params =
+ if (burnInParams.minViewY < burnInParams.topInset) {
+ // minViewY should never be below the inset. Correct it if needed
+ Log.w(TAG, "minViewY is below topInset: $burnInParams")
+ burnInParams.copy(minViewY = burnInParams.topInset)
+ } else {
+ burnInParams
+ }
return configurationInteractor
.dimensionPixelSize(R.dimen.keyguard_enter_from_top_translation_y)
.flatMapLatest { enterFromTopAmount ->
combine(
keyguardInteractor.keyguardTranslationY.onStart { emit(0f) },
- burnIn(params).map { it.translationY.toFloat() }.onStart { emit(0f) },
+ burnIn(params).onStart { emit(BurnInModel()) },
goneToAodTransitionViewModel
.enterFromTopTranslationY(enterFromTopAmount)
.onStart { emit(StateToValue()) },
@@ -88,32 +92,26 @@
aodToLockscreenTransitionViewModel.translationY(params.translationY).onStart {
emit(StateToValue())
},
- ) { keyguardTranslationY, burnInY, goneToAod, occludedToLockscreen, aodToLockscreen
- ->
- if (isInTransition(aodToLockscreen.transitionState)) {
- aodToLockscreen.value ?: 0f
- } else if (isInTransition(goneToAod.transitionState)) {
- (goneToAod.value ?: 0f) + burnInY
- } else {
- burnInY + occludedToLockscreen + keyguardTranslationY
- }
+ ) {
+ keyguardTranslationY,
+ burnInModel,
+ goneToAod,
+ occludedToLockscreen,
+ aodToLockscreen ->
+ val translationY =
+ if (isInTransition(aodToLockscreen.transitionState)) {
+ aodToLockscreen.value ?: 0f
+ } else if (isInTransition(goneToAod.transitionState)) {
+ (goneToAod.value ?: 0f) + burnInModel.translationY
+ } else {
+ burnInModel.translationY + occludedToLockscreen + keyguardTranslationY
+ }
+ burnInModel.copy(translationY = translationY.toInt())
}
}
.distinctUntilChanged()
}
- /** Scale for elements that need to apply anti-burn-in tactics. */
- fun scale(
- params: BurnInParameters,
- ): Flow<BurnInScaleViewModel> {
- return burnIn(params).map {
- BurnInScaleViewModel(
- scale = it.scale,
- scaleClockOnly = it.scaleClockOnly,
- )
- }
- }
-
private fun isInTransition(state: TransitionState): Boolean {
return state == STARTED || state == RUNNING
}
@@ -125,7 +123,10 @@
keyguardTransitionInteractor.dozeAmountTransition.map {
Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it.value)
},
- burnInInteractor.keyguardBurnIn,
+ burnInInteractor.burnIn(
+ xDimenResourceId = R.dimen.burn_in_prevention_offset_x,
+ yDimenResourceId = R.dimen.burn_in_prevention_offset_y
+ ),
) { interpolated, burnIn ->
val useScaleOnly =
(clockController(params.clockControllerProvider)
@@ -149,7 +150,6 @@
} else {
max(params.topInset, params.minViewY + burnInY) - params.minViewY
}
-
BurnInModel(
translationX = MathUtils.lerp(0, burnIn.translationX, interpolated).toInt(),
translationY = translationY,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
index 662a77e..4c0a949 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
@@ -20,6 +20,8 @@
import android.content.Context
import com.android.settingslib.Utils
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -30,12 +32,14 @@
import kotlinx.coroutines.flow.onStart
/** Models the UI state for the device entry icon background view. */
+@Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA")
@ExperimentalCoroutinesApi
class DeviceEntryBackgroundViewModel
@Inject
constructor(
val context: Context,
val deviceEntryIconViewModel: DeviceEntryIconViewModel,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
configurationInteractor: ConfigurationInteractor,
lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel,
aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
@@ -94,6 +98,23 @@
alternateBouncerToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha,
)
.merge()
+ .onStart {
+ when (
+ keyguardTransitionInteractor.currentKeyguardState.replayCache.last()
+ ) {
+ KeyguardState.GLANCEABLE_HUB,
+ KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ KeyguardState.GONE,
+ KeyguardState.OCCLUDED,
+ KeyguardState.OFF,
+ KeyguardState.DOZING,
+ KeyguardState.DREAMING,
+ KeyguardState.PRIMARY_BOUNCER,
+ KeyguardState.AOD -> emit(0f)
+ KeyguardState.ALTERNATE_BOUNCER,
+ KeyguardState.LOCKSCREEN -> emit(1f)
+ }
+ }
} else {
flowOf(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index bd19c80..1a01897 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -36,6 +36,7 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
@@ -62,6 +63,7 @@
private val deviceEntryInteractor: DeviceEntryInteractor,
private val deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
) {
+ val isUdfpsSupported: StateFlow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported
private val intEvaluator = IntEvaluator()
private val floatEvaluator = FloatEvaluator()
private val showingAlternateBouncer: Flow<Boolean> =
@@ -137,7 +139,7 @@
) { alpha, alphaMultiplier ->
alpha * alphaMultiplier
}
- val useBackgroundProtection: Flow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported
+ val useBackgroundProtection: StateFlow<Boolean> = isUdfpsSupported
val burnInOffsets: Flow<BurnInOffsets> =
deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolled ->
if (udfpsEnrolled) {
@@ -211,7 +213,7 @@
val isLongPressEnabled: Flow<Boolean> =
combine(
iconType,
- deviceEntryUdfpsInteractor.isUdfpsSupported,
+ isUdfpsSupported,
) { deviceEntryStatus, isUdfps ->
when (deviceEntryStatus) {
DeviceEntryIconView.IconType.LOCK -> isUdfps
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
index c64f277..789e4fb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
@@ -19,6 +19,7 @@
import com.android.app.animation.Interpolators.EMPHASIZED
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.res.R
@@ -36,7 +37,9 @@
constructor(
animationFlow: KeyguardTransitionAnimationFlow,
configurationInteractor: ConfigurationInteractor,
+ private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor,
) {
+ fun startTransition() = fromDreamingTransitionInteractor.startToGlanceableHubTransition()
private val transitionAnimation =
animationFlow.setup(
@@ -58,6 +61,9 @@
)
}
+ // Keep the dream visible while the hub swipes in over the dream.
+ val dreamAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(1f)
+
val dreamOverlayAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
duration = 167.milliseconds,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
index d3277cd..f191aa7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -20,16 +20,13 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filter
/**
* Breaks down DREAMING->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -40,7 +37,6 @@
class DreamingToLockscreenTransitionViewModel
@Inject
constructor(
- keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
) : DeviceEntryIconTransition {
@@ -53,12 +49,6 @@
to = KeyguardState.LOCKSCREEN,
)
- val transitionEnded =
- keyguardTransitionInteractor.fromDreamingTransition.filter { step ->
- step.transitionState == TransitionState.FINISHED ||
- step.transitionState == TransitionState.CANCELED
- }
-
/** Dream overlay y-translation on exit */
fun dreamOverlayTranslationY(translatePx: Int): Flow<Float> {
return transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
index e5b5964..abf2372 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
@@ -79,6 +79,8 @@
val notificationAlpha: Flow<Float> = keyguardAlpha
+ val shortcutsAlpha: Flow<Float> = keyguardAlpha
+
val notificationTranslationX: Flow<Float> =
keyguardTranslationX.map { it.value }.filterNotNull()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index f981fd5..58c45c7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -36,6 +36,7 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@SysUISingleton
@@ -67,7 +68,16 @@
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = LARGE
+ initialValue = LARGE,
+ )
+
+ val isLargeClockVisible =
+ clockSize
+ .map { it == LARGE }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
)
val currentClock = keyguardClockInteractor.currentClock
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
index 6458eda..e35e065 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
@@ -17,10 +17,14 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.Flags.keyguardBottomAreaRefactor
+import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.res.R
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -32,9 +36,10 @@
@Inject
constructor(
private val keyguardInteractor: KeyguardInteractor,
- bottomAreaInteractor: KeyguardBottomAreaInteractor,
+ private val bottomAreaInteractor: KeyguardBottomAreaInteractor,
keyguardBottomAreaViewModel: KeyguardBottomAreaViewModel,
private val burnInHelperWrapper: BurnInHelperWrapper,
+ private val burnInInteractor: BurnInInteractor,
private val shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel,
configurationInteractor: ConfigurationInteractor,
) {
@@ -63,24 +68,37 @@
}
.distinctUntilChanged()
}
+
+ private val burnIn: Flow<BurnInModel> =
+ burnInInteractor
+ .burnIn(
+ xDimenResourceId = R.dimen.burn_in_prevention_offset_x,
+ yDimenResourceId = R.dimen.default_burn_in_prevention_offset,
+ )
+ .distinctUntilChanged()
+
/** An observable for the x-offset by which the indication area should be translated. */
val indicationAreaTranslationX: Flow<Float> =
- if (keyguardBottomAreaRefactor()) {
- keyguardInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
+ if (migrateClocksToBlueprint() || keyguardBottomAreaRefactor()) {
+ burnIn.map { it.translationX.toFloat() }
} else {
bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
}
/** Returns an observable for the y-offset by which the indication area should be translated. */
fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> {
- return keyguardInteractor.dozeAmount
- .map { dozeAmount ->
- dozeAmount *
- (burnInHelperWrapper.burnInOffset(
- /* amplitude = */ defaultBurnInOffset * 2,
- /* xAxis= */ false,
- ) - defaultBurnInOffset)
- }
- .distinctUntilChanged()
+ return if (migrateClocksToBlueprint()) {
+ burnIn.map { it.translationY.toFloat() }
+ } else {
+ keyguardInteractor.dozeAmount
+ .map { dozeAmount ->
+ dozeAmount *
+ (burnInHelperWrapper.burnInOffset(
+ /* amplitude = */ defaultBurnInOffset * 2,
+ /* xAxis= */ false,
+ ) - defaultBurnInOffset)
+ }
+ .distinctUntilChanged()
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
index 4db942cc..c4383fc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
@@ -52,6 +52,7 @@
occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
offToLockscreenTransitionViewModel: OffToLockscreenTransitionViewModel,
primaryBouncerToLockscreenTransitionViewModel: PrimaryBouncerToLockscreenTransitionViewModel,
+ glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel,
lockscreenToDozingTransitionViewModel: LockscreenToDozingTransitionViewModel,
lockscreenToDreamingHostedTransitionViewModel: LockscreenToDreamingHostedTransitionViewModel,
@@ -59,6 +60,7 @@
lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel,
lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel,
lockscreenToPrimaryBouncerTransitionViewModel: LockscreenToPrimaryBouncerTransitionViewModel,
+ lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
transitionInteractor: KeyguardTransitionInteractor,
) {
@@ -110,6 +112,7 @@
occludedToLockscreenTransitionViewModel.shortcutsAlpha,
offToLockscreenTransitionViewModel.shortcutsAlpha,
primaryBouncerToLockscreenTransitionViewModel.shortcutsAlpha,
+ glanceableHubToLockscreenTransitionViewModel.shortcutsAlpha,
)
/** alpha while fading the quick affordances in */
@@ -122,6 +125,7 @@
lockscreenToGoneTransitionViewModel.shortcutsAlpha,
lockscreenToOccludedTransitionViewModel.shortcutsAlpha,
lockscreenToPrimaryBouncerTransitionViewModel.shortcutsAlpha,
+ lockscreenToGlanceableHubTransitionViewModel.shortcutsAlpha,
shadeExpansionAlpha,
)
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 f848717..5ca9215 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
@@ -24,9 +24,11 @@
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.BurnInModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
@@ -47,8 +49,11 @@
import com.android.systemui.util.ui.zip
import javax.inject.Inject
import kotlin.math.max
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -57,12 +62,14 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class KeyguardRootViewModel
@Inject
constructor(
+ @Application private val scope: CoroutineScope,
private val deviceEntryInteractor: DeviceEntryInteractor,
private val dozeParameters: DozeParameters,
private val keyguardInteractor: KeyguardInteractor,
@@ -102,6 +109,8 @@
private val aodAlphaViewModel: AodAlphaViewModel,
private val shadeInteractor: ShadeInteractor,
) {
+ private var burnInJob: Job? = null
+ private val burnInModel = MutableStateFlow(BurnInModel())
val burnInLayerVisibility: Flow<Int> =
keyguardTransitionInteractor.startedKeyguardState
@@ -213,21 +222,33 @@
/** For elements that appear and move during the animation -> AOD */
val burnInLayerAlpha: Flow<Float> = aodAlphaViewModel.alpha
- fun translationY(params: BurnInParameters): Flow<Float> {
- return aodBurnInViewModel.translationY(params)
- }
+ val translationY: Flow<Float> = burnInModel.map { it.translationY.toFloat() }
- fun translationX(params: BurnInParameters): Flow<StateToValue> {
- return merge(
- aodBurnInViewModel.translationX(params).map { StateToValue(to = AOD, value = it) },
+ val translationX: Flow<StateToValue> =
+ merge(
+ burnInModel.map { StateToValue(to = AOD, value = it.translationX.toFloat()) },
lockscreenToGlanceableHubTransitionViewModel.keyguardTranslationX,
glanceableHubToLockscreenTransitionViewModel.keyguardTranslationX,
)
+
+ fun updateBurnInParams(params: BurnInParameters) {
+ burnInJob?.cancel()
+
+ burnInJob =
+ scope.launch {
+ aodBurnInViewModel.movement(params).collect {
+ burnInModel.value = it
+ }
+ }
}
- fun scale(params: BurnInParameters): Flow<BurnInScaleViewModel> {
- return aodBurnInViewModel.scale(params)
- }
+ val scale: Flow<BurnInScaleViewModel> =
+ burnInModel.map {
+ BurnInScaleViewModel(
+ scale = it.scale,
+ scaleClockOnly = it.scaleClockOnly,
+ )
+ }
/** Is the notification icon container visible? */
val isNotifIconContainerVisible: Flow<AnimatedValue<Boolean>> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index b60e999..993e81b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -14,20 +14,29 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
/** Models UI state and handles user input for the lockscreen scene. */
@@ -38,30 +47,73 @@
@Application applicationScope: CoroutineScope,
deviceEntryInteractor: DeviceEntryInteractor,
communalInteractor: CommunalInteractor,
+ shadeInteractor: ShadeInteractor,
val longPress: KeyguardLongPressViewModel,
val notifications: NotificationsPlaceholderViewModel,
) {
- /** The key of the scene we should switch to when swiping up. */
- val upDestinationSceneKey: StateFlow<SceneKey> =
- deviceEntryInteractor.isUnlocked
- .map { isUnlocked -> upDestinationSceneKey(isUnlocked) }
+ val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ combine(
+ deviceEntryInteractor.isUnlocked,
+ communalInteractor.isCommunalAvailable,
+ shadeInteractor.shadeMode,
+ ) { isDeviceUnlocked, isCommunalAvailable, shadeMode ->
+ destinationScenes(
+ isDeviceUnlocked = isDeviceUnlocked,
+ isCommunalAvailable = isCommunalAvailable,
+ shadeMode = shadeMode,
+ )
+ }
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = upDestinationSceneKey(deviceEntryInteractor.isUnlocked.value),
+ initialValue =
+ destinationScenes(
+ isDeviceUnlocked = deviceEntryInteractor.isUnlocked.value,
+ isCommunalAvailable = false,
+ shadeMode = shadeInteractor.shadeMode.value,
+ ),
)
- private fun upDestinationSceneKey(isUnlocked: Boolean): SceneKey {
- return if (isUnlocked) Scenes.Gone else Scenes.Bouncer
+ private fun destinationScenes(
+ isDeviceUnlocked: Boolean,
+ isCommunalAvailable: Boolean,
+ shadeMode: ShadeMode,
+ ): Map<UserAction, UserActionResult> {
+ val quickSettingsIfSingleShade =
+ if (shadeMode is ShadeMode.Single) {
+ Scenes.QuickSettings
+ } else {
+ Scenes.Shade
+ }
+
+ return mapOf(
+ Swipe.Left to UserActionResult(Scenes.Communal).takeIf { isCommunalAvailable },
+ Swipe.Up to if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer,
+
+ // Swiping down from the top edge goes to QS (or shade if in split shade mode).
+ swipeDownFromTop(pointerCount = 1) to quickSettingsIfSingleShade,
+ swipeDownFromTop(pointerCount = 2) to quickSettingsIfSingleShade,
+
+ // Swiping down, not from the edge, always navigates to the shade scene.
+ swipeDown(pointerCount = 1) to Scenes.Shade,
+ swipeDown(pointerCount = 2) to Scenes.Shade,
+ )
+ .filterValues { it != null }
+ .mapValues { checkNotNull(it.value) }
}
- /** The key of the scene we should switch to when swiping left. */
- val leftDestinationSceneKey: StateFlow<SceneKey?> =
- communalInteractor.isCommunalAvailable
- .map { available -> if (available) Scenes.Communal else null }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = null,
- )
+ private fun swipeDownFromTop(pointerCount: Int): Swipe {
+ return Swipe(
+ SwipeDirection.Down,
+ fromSource = Edge.Top,
+ pointerCount = pointerCount,
+ )
+ }
+
+ private fun swipeDown(pointerCount: Int): Swipe {
+ return Swipe(
+ SwipeDirection.Down,
+ pointerCount = pointerCount,
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
index 978e71e..b7f7b06 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
@@ -81,6 +81,8 @@
val notificationAlpha: Flow<Float> = keyguardAlpha
+ val shortcutsAlpha: Flow<Float> = keyguardAlpha
+
val notificationTranslationX: Flow<Float> =
keyguardTranslationX.map { it.value }.filterNotNull()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
index 027a739..bf22563 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
@@ -19,6 +19,7 @@
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import javax.inject.Inject
-class MediaCarouselViewModel @Inject constructor(mediaDataManager: MediaDataManager) {
- val isMediaVisible: Boolean = mediaDataManager.hasActiveMediaOrRecommendation()
+class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
+ val isMediaVisible: Boolean
+ get() = mediaDataManager.hasActiveMediaOrRecommendation()
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index f4903f1..768bb8e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -131,6 +131,7 @@
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.rotation.RotationButton;
@@ -199,6 +200,7 @@
private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
private final KeyguardStateController mKeyguardStateController;
private final ShadeViewController mShadeViewController;
+ private final PanelExpansionInteractor mPanelExpansionInteractor;
private final NotificationRemoteInputManager mNotificationRemoteInputManager;
private final OverviewProxyService mOverviewProxyService;
private final NavigationModeController mNavigationModeController;
@@ -537,6 +539,7 @@
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
KeyguardStateController keyguardStateController,
ShadeViewController shadeViewController,
+ PanelExpansionInteractor panelExpansionInteractor,
NotificationRemoteInputManager notificationRemoteInputManager,
NotificationShadeDepthController notificationShadeDepthController,
@Main Handler mainHandler,
@@ -575,6 +578,7 @@
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
mKeyguardStateController = keyguardStateController;
mShadeViewController = shadeViewController;
+ mPanelExpansionInteractor = panelExpansionInteractor;
mNotificationRemoteInputManager = notificationRemoteInputManager;
mOverviewProxyService = overviewProxyService;
mNavigationModeController = navigationModeController;
@@ -749,7 +753,7 @@
final Display display = mView.getDisplay();
mView.setComponents(mRecentsOptional);
if (mCentralSurfacesOptionalLazy.get().isPresent()) {
- mView.setComponents(mShadeViewController);
+ mView.setComponents(mShadeViewController, mPanelExpansionInteractor);
}
mView.setDisabledFlags(mDisabledFlags1, mSysUiFlagsContainer);
mView.setOnVerticalChangedListener(this::onVerticalChanged);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index c5190a2..1927f49 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -75,6 +75,7 @@
import com.android.systemui.res.R;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shared.rotation.FloatingRotationButton;
import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback;
import com.android.systemui.shared.rotation.RotationButtonController;
@@ -149,7 +150,9 @@
private NavigationBarInflaterView mNavigationInflaterView;
private Optional<Recents> mRecentsOptional = Optional.empty();
@Nullable
- private ShadeViewController mPanelView;
+ private ShadeViewController mShadeViewController;
+ @Nullable
+ private PanelExpansionInteractor mPanelExpansionInteractor;
private RotationContextButton mRotationContextButton;
private FloatingRotationButton mFloatingRotationButton;
private RotationButtonController mRotationButtonController;
@@ -347,8 +350,9 @@
}
/** */
- public void setComponents(ShadeViewController panel) {
- mPanelView = panel;
+ public void setComponents(ShadeViewController svc, PanelExpansionInteractor pei) {
+ mShadeViewController = svc;
+ mPanelExpansionInteractor = pei;
updatePanelSystemUiStateFlags();
}
@@ -750,10 +754,10 @@
private void updatePanelSystemUiStateFlags() {
if (SysUiState.DEBUG) {
- Log.d(TAG, "Updating panel sysui state flags: panelView=" + mPanelView);
+ Log.d(TAG, "Updating panel sysui state flags: panelView=" + mShadeViewController);
}
- if (mPanelView != null) {
- mPanelView.updateSystemUiStateFlags();
+ if (mShadeViewController != null) {
+ mShadeViewController.updateSystemUiStateFlags();
}
}
@@ -801,7 +805,8 @@
*/
void updateSlippery() {
setSlippery(!isQuickStepSwipeUpEnabled() ||
- (mPanelView != null && mPanelView.isFullyExpanded() && !mPanelView.isCollapsing()));
+ (mPanelExpansionInteractor != null && mPanelExpansionInteractor.isFullyExpanded()
+ && !mPanelExpansionInteractor.isCollapsing()));
}
void setSlippery(boolean slippery) {
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 91c86df..9d0ea5e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -19,6 +19,7 @@
import static android.view.InputDevice.SOURCE_TOUCHPAD;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
+import static com.android.systemui.Flags.edgebackGestureHandlerGetRunningTasksBackground;
import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll;
import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe;
@@ -54,7 +55,6 @@
import android.view.IWindowManager;
import android.view.InputDevice;
import android.view.InputEvent;
-import android.view.InputMonitor;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -104,6 +104,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import javax.inject.Inject;
@@ -151,7 +152,12 @@
private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
@Override
public void onTaskStackChanged() {
- mGestureBlockingActivityRunning = isGestureBlockingActivityRunning();
+ if (edgebackGestureHandlerGetRunningTasksBackground()) {
+ mBackgroundExecutor.execute(() -> mGestureBlockingActivityRunning.set(
+ isGestureBlockingActivityRunning()));
+ } else {
+ mGestureBlockingActivityRunning.set(isGestureBlockingActivityRunning());
+ }
}
@Override
public void onTaskCreated(int taskId, ComponentName componentName) {
@@ -241,6 +247,8 @@
private final PointF mDownPoint = new PointF();
private final PointF mEndPoint = new PointF();
+ private AtomicBoolean mGestureBlockingActivityRunning = new AtomicBoolean();
+
private boolean mThresholdCrossed = false;
private boolean mAllowGesture = false;
private boolean mLogGesture = false;
@@ -256,7 +264,6 @@
private boolean mIsEnabled;
private boolean mIsNavBarShownTransiently;
private boolean mIsBackGestureAllowed;
- private boolean mGestureBlockingActivityRunning;
private boolean mIsNewBackAffordanceEnabled;
private boolean mIsTrackpadGestureFeaturesEnabled;
private boolean mIsTrackpadThreeFingerSwipe;
@@ -1017,7 +1024,7 @@
mInRejectedExclusion = false;
boolean isWithinInsets = isWithinInsets((int) ev.getX(), (int) ev.getY());
boolean isBackAllowedCommon = !mDisabledForQuickstep && mIsBackGestureAllowed
- && !mGestureBlockingActivityRunning
+ && !mGestureBlockingActivityRunning.get()
&& !QuickStepContract.isBackGestureDisabled(mSysUiFlags,
mIsTrackpadThreeFingerSwipe)
&& !isTrackpadScroll(mIsTrackpadGestureFeaturesEnabled, ev);
@@ -1053,8 +1060,8 @@
curTime, curTimeStr, mAllowGesture, mIsTrackpadThreeFingerSwipe,
mIsOnLeftEdge, mDeferSetIsOnLeftEdge, mIsBackGestureAllowed,
QuickStepContract.isBackGestureDisabled(mSysUiFlags,
- mIsTrackpadThreeFingerSwipe),
- mDisabledForQuickstep, mGestureBlockingActivityRunning, mIsInPip, mDisplaySize,
+ mIsTrackpadThreeFingerSwipe), mDisabledForQuickstep,
+ mGestureBlockingActivityRunning.get(), mIsInPip, mDisplaySize,
mEdgeWidthLeft, mLeftInset, mEdgeWidthRight, mRightInset, mExcludeRegion));
} else if (mAllowGesture || mLogGesture) {
if (!mThresholdCrossed) {
@@ -1236,7 +1243,7 @@
pw.println(" mIsBackGestureAllowed=" + mIsBackGestureAllowed);
pw.println(" mIsGestureHandlingEnabled=" + mIsGestureHandlingEnabled);
pw.println(" mIsNavBarShownTransiently=" + mIsNavBarShownTransiently);
- pw.println(" mGestureBlockingActivityRunning=" + mGestureBlockingActivityRunning);
+ pw.println(" mGestureBlockingActivityRunning=" + mGestureBlockingActivityRunning.get());
pw.println(" mAllowGesture=" + mAllowGesture);
pw.println(" mUseMLModel=" + mUseMLModel);
pw.println(" mDisabledForQuickstep=" + mDisabledForQuickstep);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 2440651..cd65119 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -38,6 +38,7 @@
import com.android.systemui.settings.brightness.BrightnessController;
import com.android.systemui.settings.brightness.BrightnessMirrorHandler;
import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
@@ -90,9 +91,11 @@
FalsingManager falsingManager,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
SplitShadeStateController splitShadeStateController,
- SceneContainerFlags sceneContainerFlags) {
+ SceneContainerFlags sceneContainerFlags,
+ VibratorHelper vibratorHelper) {
super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost,
- metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController);
+ metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController,
+ vibratorHelper);
mTunerService = tunerService;
mQsCustomizerController = qsCustomizerController;
mQsTileRevealControllerFactory = qsTileRevealControllerFactory;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 975c871..5e12b9d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -39,6 +39,7 @@
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileViewImpl;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.animation.DisappearParameters;
@@ -87,6 +88,8 @@
private SplitShadeStateController mSplitShadeStateController;
+ private final VibratorHelper mVibratorHelper;
+
@VisibleForTesting
protected final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
new QSPanel.OnConfigurationChangedListener() {
@@ -144,7 +147,8 @@
UiEventLogger uiEventLogger,
QSLogger qsLogger,
DumpManager dumpManager,
- SplitShadeStateController splitShadeStateController
+ SplitShadeStateController splitShadeStateController,
+ VibratorHelper vibratorHelper
) {
super(view);
mHost = host;
@@ -158,6 +162,7 @@
mSplitShadeStateController = splitShadeStateController;
mShouldUseSplitNotificationShade =
mSplitShadeStateController.shouldUseSplitNotificationShade(getResources());
+ mVibratorHelper = vibratorHelper;
}
@Override
@@ -300,7 +305,8 @@
}
private void addTile(final QSTile tile, boolean collapsedView) {
- final QSTileViewImpl tileView = new QSTileViewImpl(getContext(), collapsedView);
+ final QSTileViewImpl tileView = new QSTileViewImpl(
+ getContext(), collapsedView, mVibratorHelper);
final TileRecord r = new TileRecord(tile, tileView);
// TODO(b/250618218): Remove the QSLogger in QSTileViewImpl once we know the root cause of
// b/250618218.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index a8e88da..05bb088 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -32,6 +32,7 @@
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.util.leak.RotationUtils;
@@ -56,10 +57,11 @@
@Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA)
Provider<Boolean> usingCollapsedLandscapeMediaProvider,
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
- DumpManager dumpManager, SplitShadeStateController splitShadeStateController
+ DumpManager dumpManager, SplitShadeStateController splitShadeStateController,
+ VibratorHelper vibratorHelper
) {
super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
- uiEventLogger, qsLogger, dumpManager, splitShadeStateController);
+ uiEventLogger, qsLogger, dumpManager, splitShadeStateController, vibratorHelper);
mUsingCollapsedLandscapeMediaProvider = usingCollapsedLandscapeMediaProvider;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index e5af8e6..58858df 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -46,7 +46,6 @@
import com.android.systemui.FontSizeUtils;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
-import com.android.systemui.res.R;
import com.android.systemui.qs.QSEditEvent;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.TileLayout;
@@ -57,6 +56,7 @@
import com.android.systemui.qs.dagger.QSThemedContext;
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.tileimpl.QSTileViewImpl;
+import com.android.systemui.res.R;
import java.util.ArrayList;
import java.util.List;
@@ -319,6 +319,9 @@
}
FrameLayout frame = (FrameLayout) inflater.inflate(R.layout.qs_customize_tile_frame, parent,
false);
+ if (com.android.systemui.Flags.qsTileFocusState()) {
+ frame.setClipChildren(false);
+ }
View view = new CustomizeTileView(context);
frame.addView(view);
return new Holder(frame);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt
new file mode 100644
index 0000000..a2ded6a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.tileimpl
+
+/**
+ * List of properties that define the state of a tile during a long-press gesture.
+ *
+ * These properties are used during animation if a tile supports a long-press action.
+ */
+data class QSLongPressProperties(
+ var xScale: Float,
+ var yScale: Float,
+ var cornerRadius: Float,
+ var backgroundColor: Int,
+ var labelColor: Int,
+ var secondaryLabelColor: Int,
+ var chevronColor: Int,
+ var overlayColor: Int,
+ var iconColor: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 35cac4b..1456747 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -587,7 +587,7 @@
name = "handleClick";
if (mState.disabledByPolicy) {
Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
- mContext, mEnforcedAdmin);
+ mEnforcedAdmin);
mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
} else {
mQSLogger.logHandleClick(mTileSpec, msg.arg1);
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 9fefcbd..63963de 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -26,6 +26,7 @@
import android.graphics.Color
import android.graphics.PorterDuff
import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.LayerDrawable
import android.graphics.drawable.RippleDrawable
import android.os.Trace
@@ -36,6 +37,7 @@
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
+import android.view.ViewConfiguration
import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
@@ -47,9 +49,13 @@
import androidx.annotation.VisibleForTesting
import com.android.app.tracing.traceSection
import com.android.settingslib.Utils
+import com.android.systemui.Flags
+import com.android.systemui.Flags.quickSettingsVisualHapticsLongpress
import com.android.systemui.FontSizeUtils
import com.android.systemui.animation.LaunchableView
import com.android.systemui.animation.LaunchableViewDelegate
+import com.android.systemui.haptics.qs.QSLongPressEffect
+import com.android.systemui.haptics.qs.QSLongPressEffectViewBinder
import com.android.systemui.plugins.qs.QSIconView
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.qs.QSTile.AdapterState
@@ -57,12 +63,15 @@
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH
import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.children
import java.util.Objects
private const val TAG = "QSTileViewImpl"
open class QSTileViewImpl @JvmOverloads constructor(
context: Context,
- private val collapsed: Boolean = false
+ private val collapsed: Boolean = false,
+ private val vibratorHelper: VibratorHelper? = null,
) : QSTileView(context), HeightOverrideable, LaunchableView {
companion object {
@@ -134,7 +143,7 @@
*/
protected var showRippleEffect = true
- private lateinit var ripple: RippleDrawable
+ private lateinit var qsTileBackground: LayerDrawable
private lateinit var backgroundDrawable: LayerDrawable
private lateinit var backgroundBaseDrawable: Drawable
private lateinit var backgroundOverlayDrawable: Drawable
@@ -162,6 +171,7 @@
private var lastStateDescription: CharSequence? = null
private var tileState = false
private var lastState = INVALID
+ private var lastIconTint = 0
private val launchableViewDelegate = LaunchableViewDelegate(
this,
superSetVisibility = { super.setVisibility(it) },
@@ -170,6 +180,12 @@
private val locInScreen = IntArray(2)
+ /** Visuo-haptic long-press effects */
+ private var longPressEffect: QSLongPressEffect? = null
+ private var initialLongPressProperties: QSLongPressProperties? = null
+ private var finalLongPressProperties: QSLongPressProperties? = null
+ private val colorEvaluator = ArgbEvaluator.getInstance()
+
init {
val typedValue = TypedValue()
if (!getContext().theme.resolveAttribute(R.attr.isQsTheme, typedValue, true)) {
@@ -283,15 +299,20 @@
addView(sideView)
}
- fun createTileBackground(): Drawable {
- ripple = mContext.getDrawable(R.drawable.qs_tile_background) as RippleDrawable
- backgroundDrawable = ripple.findDrawableByLayerId(R.id.background) as LayerDrawable
+ private fun createTileBackground(): Drawable {
+ qsTileBackground = if (Flags.qsTileFocusState()) {
+ mContext.getDrawable(R.drawable.qs_tile_background_flagged) as LayerDrawable
+ } else {
+ mContext.getDrawable(R.drawable.qs_tile_background) as RippleDrawable
+ }
+ backgroundDrawable =
+ qsTileBackground.findDrawableByLayerId(R.id.background) as LayerDrawable
backgroundBaseDrawable =
backgroundDrawable.findDrawableByLayerId(R.id.qs_tile_background_base)
backgroundOverlayDrawable =
backgroundDrawable.findDrawableByLayerId(R.id.qs_tile_background_overlay)
backgroundOverlayDrawable.mutate().setTintMode(PorterDuff.Mode.SRC)
- return ripple
+ return qsTileBackground
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
@@ -333,6 +354,9 @@
true
}
)
+ if (quickSettingsVisualHapticsLongpress()) {
+ isHapticFeedbackEnabled = false // Haptics will be handled by the [QSLongPressEffect]
+ }
}
private fun init(
@@ -366,14 +390,16 @@
override fun setClickable(clickable: Boolean) {
super.setClickable(clickable)
- background = if (clickable && showRippleEffect) {
- ripple.also {
- // In case that the colorBackgroundDrawable was used as the background, make sure
- // it has the correct callback instead of null
- backgroundDrawable.callback = it
+ if (!Flags.qsTileFocusState()){
+ background = if (clickable && showRippleEffect) {
+ qsTileBackground.also {
+ // In case that the colorBackgroundDrawable was used as the background, make sure
+ // it has the correct callback instead of null
+ backgroundDrawable.callback = it
+ }
+ } else {
+ backgroundDrawable
}
- } else {
- backgroundDrawable
}
}
@@ -581,6 +607,27 @@
lastState = state.state
lastDisabledByPolicy = state.disabledByPolicy
+ lastIconTint = icon.getColor(state)
+
+ // Long-press effects
+ if (quickSettingsVisualHapticsLongpress()){
+ if (state.handlesLongClick) {
+ // initialize the long-press effect and set it as the touch listener
+ showRippleEffect = false
+ initializeLongPressEffect()
+ setOnTouchListener(longPressEffect)
+ QSLongPressEffectViewBinder.bind(this, longPressEffect)
+ } else {
+ // Long-press effects might have been enabled before but the new state does not
+ // handle a long-press. In this case, we go back to the behaviour of a regular tile
+ // and clean-up the resources
+ showRippleEffect = isClickable
+ setOnTouchListener(null)
+ longPressEffect = null
+ initialLongPressProperties = null
+ finalLongPressProperties = null
+ }
+ }
}
private fun setAllColors(
@@ -701,6 +748,140 @@
}
}
+ override fun onActivityLaunchAnimationEnd() = resetLongPressEffectProperties()
+
+ fun updateLongPressEffectProperties(effectProgress: Float) {
+ if (!isLongClickable) return
+ setAllColors(
+ colorEvaluator.evaluate(
+ effectProgress,
+ initialLongPressProperties?.backgroundColor ?: 0,
+ finalLongPressProperties?.backgroundColor ?: 0,
+ ) as Int,
+ colorEvaluator.evaluate(
+ effectProgress,
+ initialLongPressProperties?.labelColor ?: 0,
+ finalLongPressProperties?.labelColor ?: 0,
+ ) as Int,
+ colorEvaluator.evaluate(
+ effectProgress,
+ initialLongPressProperties?.secondaryLabelColor ?: 0,
+ finalLongPressProperties?.secondaryLabelColor ?: 0,
+ ) as Int,
+ colorEvaluator.evaluate(
+ effectProgress,
+ initialLongPressProperties?.chevronColor ?: 0,
+ finalLongPressProperties?.chevronColor ?: 0,
+ ) as Int,
+ colorEvaluator.evaluate(
+ effectProgress,
+ initialLongPressProperties?.overlayColor ?: 0,
+ finalLongPressProperties?.overlayColor ?: 0,
+ ) as Int,
+ )
+ icon.setTint(
+ icon.mIcon as ImageView,
+ colorEvaluator.evaluate(
+ effectProgress,
+ initialLongPressProperties?.iconColor ?: 0,
+ finalLongPressProperties?.iconColor ?: 0,
+ ) as Int,
+ )
+
+ val newScaleX =
+ interpolateFloat(
+ effectProgress,
+ initialLongPressProperties?.xScale ?: 1f,
+ finalLongPressProperties?.xScale ?: 1f,
+ )
+ val newScaleY =
+ interpolateFloat(
+ effectProgress,
+ initialLongPressProperties?.xScale ?: 1f,
+ finalLongPressProperties?.xScale ?: 1f,
+ )
+ val newRadius =
+ interpolateFloat(
+ effectProgress,
+ initialLongPressProperties?.cornerRadius ?: 0f,
+ finalLongPressProperties?.cornerRadius ?: 0f,
+ )
+ scaleX = newScaleX
+ scaleY = newScaleY
+ for (child in children) {
+ child.scaleX = 1f / newScaleX
+ child.scaleY = 1f / newScaleY
+ }
+ changeCornerRadius(newRadius)
+ }
+
+ private fun interpolateFloat(fraction: Float, start: Float, end: Float): Float =
+ start + fraction * (end - start)
+
+ private fun resetLongPressEffectProperties() {
+ scaleY = 1f
+ scaleX = 1f
+ for (child in children) {
+ child.scaleY = 1f
+ child.scaleX = 1f
+ }
+ changeCornerRadius(resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat())
+ setAllColors(
+ getBackgroundColorForState(lastState, lastDisabledByPolicy),
+ getLabelColorForState(lastState, lastDisabledByPolicy),
+ getSecondaryLabelColorForState(lastState, lastDisabledByPolicy),
+ getChevronColorForState(lastState, lastDisabledByPolicy),
+ getOverlayColorForState(lastState),
+ )
+ icon.setTint(icon.mIcon as ImageView, lastIconTint)
+ }
+
+ private fun initializeLongPressEffect() {
+ initializeLongPressProperties()
+ longPressEffect =
+ QSLongPressEffect(
+ vibratorHelper,
+ ViewConfiguration.getLongPressTimeout() - ViewConfiguration.getTapTimeout(),
+ )
+ }
+
+ private fun initializeLongPressProperties() {
+ initialLongPressProperties =
+ QSLongPressProperties(
+ /* xScale= */1f,
+ /* yScale= */1f,
+ resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat(),
+ getBackgroundColorForState(lastState),
+ getLabelColorForState(lastState),
+ getSecondaryLabelColorForState(lastState),
+ getChevronColorForState(lastState),
+ getOverlayColorForState(lastState),
+ lastIconTint,
+ )
+
+ finalLongPressProperties =
+ QSLongPressProperties(
+ /* xScale= */1.1f,
+ /* yScale= */1.2f,
+ resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat() - 20,
+ getBackgroundColorForState(Tile.STATE_ACTIVE),
+ getLabelColorForState(Tile.STATE_ACTIVE),
+ getSecondaryLabelColorForState(Tile.STATE_ACTIVE),
+ getChevronColorForState(Tile.STATE_ACTIVE),
+ getOverlayColorForState(Tile.STATE_ACTIVE),
+ Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive),
+ )
+ }
+
+ private fun changeCornerRadius(radius: Float) {
+ for (i in 0 until backgroundDrawable.numberOfLayers) {
+ val layer = backgroundDrawable.getDrawable(i)
+ if (layer is GradientDrawable) {
+ layer.cornerRadius = radius
+ }
+ }
+ }
+
@VisibleForTesting
internal fun getCurrentColors(): List<Int> = listOf(
backgroundColor,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
index 20f3c4d..bd66843 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -103,14 +103,27 @@
public override fun handleClick(view: View?) {
if (isRecording) {
isRecording = false
- stopScreenRecord()
+ stopIssueRecordingService()
} else {
mUiHandler.post { showPrompt(view) }
}
refreshState()
}
- private fun stopScreenRecord() =
+ private fun startIssueRecordingService(screenRecord: Boolean, winscopeTracing: Boolean) =
+ PendingIntent.getForegroundService(
+ userContextProvider.userContext,
+ RecordingService.REQUEST_CODE,
+ IssueRecordingService.getStartIntent(
+ userContextProvider.userContext,
+ screenRecord,
+ winscopeTracing
+ ),
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+ .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle())
+
+ private fun stopIssueRecordingService() =
PendingIntent.getService(
userContextProvider.userContext,
RecordingService.REQUEST_CODE,
@@ -124,6 +137,7 @@
delegateFactory
.create {
isRecording = true
+ startIssueRecordingService(it.screenRecord, it.winscopeTracing)
refreshState()
}
.createDialog()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index 32deb30..6b654be 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -34,11 +34,11 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.drawable.CircleFramedDrawable;
-import com.android.systemui.res.R;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qs.PseudoGridView;
import com.android.systemui.qs.QSUserSwitcherEvent;
import com.android.systemui.qs.user.UserSwitchDialogController;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter;
import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -186,7 +186,7 @@
(UserRecord) view.getTag();
if (userRecord.isDisabledByAdmin()) {
final Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
- mContext, userRecord.enforcedAdmin);
+ userRecord.enforcedAdmin);
mController.startActivity(intent);
} else if (userRecord.isSwitchToEnabled) {
MetricsLogger.action(mContext, MetricsEvent.QS_SWITCH_USER);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt
index d1f8945..87b89ea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt
@@ -96,10 +96,7 @@
is PolicyResult.TileEnabled -> false
is PolicyResult.TileDisabled -> {
val intent =
- RestrictedLockUtils.getShowAdminSupportDetailsIntent(
- context,
- policyResult.admin
- )
+ RestrictedLockUtils.getShowAdminSupportDetailsIntent(policyResult.admin)
activityStarter.postStartActivityDismissingKeyguard(intent, 0)
true
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegate.kt
index 7ece6e0..9d53703 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegate.kt
@@ -32,6 +32,7 @@
import android.widget.ProgressBar
import android.widget.Switch
import android.widget.TextView
+import androidx.annotation.StringRes
import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
@@ -204,12 +205,17 @@
getAutoOnToggleView(dialog).visibility = uiProperties.autoOnToggleVisibility
}
- internal fun onBluetoothAutoOnUpdated(dialog: SystemUIDialog, isEnabled: Boolean) {
+ internal fun onBluetoothAutoOnUpdated(
+ dialog: SystemUIDialog,
+ isEnabled: Boolean,
+ @StringRes infoResId: Int
+ ) {
getAutoOnToggle(dialog).apply {
isChecked = isEnabled
setEnabled(true)
alpha = ENABLED_ALPHA
}
+ getAutoOnToggleInfoTextView(dialog).text = context.getString(infoResId)
}
private fun setupToggle(dialog: SystemUIDialog) {
@@ -264,6 +270,10 @@
return dialog.requireViewById(R.id.bluetooth_auto_on_toggle_layout)
}
+ private fun getAutoOnToggleInfoTextView(dialog: SystemUIDialog): TextView {
+ return dialog.requireViewById(R.id.bluetooth_auto_on_toggle_info_text)
+ }
+
private fun getProgressBarAnimation(dialog: SystemUIDialog): ProgressBar {
return dialog.requireViewById(R.id.bluetooth_tile_dialog_progress_animation)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
index 0486207..e4f3c19 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
@@ -191,7 +191,14 @@
// bluetoothAutoOnUpdate is emitted when bluetooth auto on on/off state is
// changed.
bluetoothAutoOnInteractor.isEnabled
- .onEach { dialogDelegate.onBluetoothAutoOnUpdated(dialog, it) }
+ .onEach {
+ dialogDelegate.onBluetoothAutoOnUpdated(
+ dialog,
+ it,
+ if (it) R.string.turn_on_bluetooth_auto_info_enabled
+ else R.string.turn_on_bluetooth_auto_info_disabled
+ )
+ }
.launchIn(this)
// bluetoothAutoOnToggle is emitted when user toggles the bluetooth auto on
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 5f8b5dd..d0ff338 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -33,6 +33,7 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
@@ -111,6 +112,7 @@
import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
import com.android.systemui.statusbar.policy.CallbackController;
import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder;
+import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.sysui.ShellInterface;
import dagger.Lazy;
@@ -673,9 +675,13 @@
}
@Override
- public void enterStageSplitFromRunningApp(boolean leftOrTop) {
+ public void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop) {
if (mOverviewProxy != null) {
try {
+ if (DesktopModeStatus.isEnabled() && (sysUiState.getFlags()
+ & SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0) {
+ return;
+ }
mOverviewProxy.enterStageSplitFromRunningApp(leftOrTop);
} catch (RemoteException e) {
Log.w(TAG_OPS, "Unable to enter stage split from the current running app");
diff --git a/core/java/android/app/ondeviceintelligence/Content.aidl b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingConfig.kt
similarity index 67%
copy from core/java/android/app/ondeviceintelligence/Content.aidl
copy to packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingConfig.kt
index 40f0ef9..bb3b654 100644
--- a/core/java/android/app/ondeviceintelligence/Content.aidl
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingConfig.kt
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2024, The Android Open Source Project
+/*
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-package android.app.ondeviceintelligence;
+package com.android.systemui.recordissue
-/**
- * @hide
- */
-parcelable Content;
+data class IssueRecordingConfig(val screenRecord: Boolean, val winscopeTracing: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index f487258..8a84496 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -19,8 +19,13 @@
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
+import android.content.pm.LauncherApps
import android.content.res.Resources
+import android.net.Uri
import android.os.Handler
+import android.os.UserHandle
+import android.util.Log
+import androidx.core.content.FileProvider
import com.android.internal.logging.UiEventLogger
import com.android.systemui.dagger.qualifiers.LongRunning
import com.android.systemui.dagger.qualifiers.Main
@@ -30,7 +35,14 @@
import com.android.systemui.screenrecord.RecordingServiceStrings
import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
+import com.android.traceur.FileSender
+import com.android.traceur.TraceUtils
+import java.io.File
+import java.io.FileOutputStream
+import java.nio.file.Files
import java.util.concurrent.Executor
+import java.util.zip.ZipEntry
+import java.util.zip.ZipOutputStream
import javax.inject.Inject
class IssueRecordingService
@@ -60,9 +72,122 @@
override fun provideRecordingServiceStrings(): RecordingServiceStrings = IrsStrings(resources)
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ when (intent?.action) {
+ ACTION_START -> {
+ TraceUtils.traceStart(
+ contentResolver,
+ DEFAULT_TRACE_TAGS,
+ DEFAULT_BUFFER_SIZE,
+ DEFAULT_IS_INCLUDING_WINSCOPE,
+ DEFAULT_IS_INCLUDING_APP_TRACE,
+ DEFAULT_IS_LONG_TRACE,
+ DEFAULT_ATTACH_TO_BUGREPORT,
+ DEFAULT_MAX_TRACE_SIZE,
+ DEFAULT_MAX_TRACE_DURATION_IN_MINUTES
+ )
+ if (!intent.getBooleanExtra(EXTRA_SCREEN_RECORD, false)) {
+ // If we don't want to record the screen, the ACTION_SHOW_START_NOTIF action
+ // will circumvent the RecordingService's screen recording start code.
+ return super.onStartCommand(Intent(ACTION_SHOW_START_NOTIF), flags, startId)
+ }
+ }
+ ACTION_STOP,
+ ACTION_STOP_NOTIF -> {
+ // ViewCapture needs to save it's data before it is disabled, or else the data will
+ // be lost. This is expected to change in the near future, and when that happens
+ // this line should be removed.
+ getSystemService(LauncherApps::class.java)?.saveViewCaptureData()
+ TraceUtils.traceStop(contentResolver)
+ }
+ ACTION_SHARE -> {
+ shareRecording(intent)
+
+ // Unlike all other actions, action_share has different behavior for the screen
+ // recording qs tile than it does for the record issue qs tile. Return sticky to
+ // avoid running any of the base class' code for this action.
+ return START_STICKY
+ }
+ else -> {}
+ }
+ return super.onStartCommand(intent, flags, startId)
+ }
+
+ private fun shareRecording(intent: Intent) {
+ val sharableUri: Uri =
+ zipAndPackageRecordings(
+ TraceUtils.traceDump(contentResolver, TRACE_FILE_NAME).get(),
+ intent.getStringExtra(EXTRA_PATH)
+ )
+ ?: return
+ val sendIntent =
+ FileSender.buildSendIntent(this, listOf(sharableUri))
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
+ if (mNotificationId != NOTIF_BASE_ID) {
+ mNotificationManager.cancelAsUser(
+ null,
+ mNotificationId,
+ UserHandle(mUserContextTracker.userContext.userId)
+ )
+ }
+
+ // TODO: Debug why the notification shade isn't closing upon starting the BetterBug activity
+ mKeyguardDismissUtil.executeWhenUnlocked(
+ {
+ startActivity(sendIntent)
+ false
+ },
+ false,
+ false
+ )
+ }
+
+ private fun zipAndPackageRecordings(traceFiles: List<File>, screenRecordingUri: String?): Uri? {
+ try {
+ externalCacheDir?.mkdirs()
+ val outZip: File = File.createTempFile(TEMP_FILE_PREFIX, ZIP_SUFFIX, externalCacheDir)
+ ZipOutputStream(FileOutputStream(outZip)).use { os ->
+ traceFiles.forEach { file ->
+ os.putNextEntry(ZipEntry(file.name))
+ Files.copy(file.toPath(), os)
+ os.closeEntry()
+ }
+ if (screenRecordingUri != null) {
+ contentResolver.openInputStream(Uri.parse(screenRecordingUri))?.use {
+ os.putNextEntry(ZipEntry(SCREEN_RECORDING_ZIP_LABEL))
+ it.transferTo(os)
+ os.closeEntry()
+ }
+ }
+ }
+ return FileProvider.getUriForFile(this, AUTHORITY, outZip)
+ } catch (e: Exception) {
+ Log.e(TAG, "Failed to zip and package Recordings. Cannot share with BetterBug.", e)
+ return null
+ }
+ }
+
companion object {
private const val TAG = "IssueRecordingService"
private const val CHANNEL_ID = "issue_record"
+ private const val EXTRA_SCREEN_RECORD = "extra_screenRecord"
+ private const val EXTRA_WINSCOPE_TRACING = "extra_winscopeTracing"
+ private const val ZIP_SUFFIX = ".zip"
+ private const val TEMP_FILE_PREFIX = "issue_recording"
+ private const val SCREEN_RECORDING_ZIP_LABEL = "screen-recording.mp4"
+
+ private val DEFAULT_TRACE_TAGS = listOf<String>()
+ private const val DEFAULT_BUFFER_SIZE = 16384
+ private const val DEFAULT_IS_INCLUDING_WINSCOPE = true
+ private const val DEFAULT_IS_LONG_TRACE = false
+ private const val DEFAULT_IS_INCLUDING_APP_TRACE = true
+ private const val DEFAULT_ATTACH_TO_BUGREPORT = true
+ private const val DEFAULT_MAX_TRACE_SIZE = 10240
+ private const val DEFAULT_MAX_TRACE_DURATION_IN_MINUTES = 30
+
+ private val TRACE_FILE_NAME = TraceUtils.getOutputFilename(TraceUtils.RecordingType.TRACE)
+ private const val AUTHORITY = "com.android.systemui.fileprovider"
/**
* Get an intent to stop the issue recording service.
@@ -71,7 +196,7 @@
* @return
*/
fun getStopIntent(context: Context): Intent =
- Intent(context, RecordingService::class.java)
+ Intent(context, IssueRecordingService::class.java)
.setAction(ACTION_STOP)
.putExtra(Intent.EXTRA_USER_HANDLE, context.userId)
@@ -80,8 +205,15 @@
*
* @param context Context from the requesting activity
*/
- fun getStartIntent(context: Context): Intent =
- Intent(context, RecordingService::class.java).setAction(ACTION_START)
+ fun getStartIntent(
+ context: Context,
+ screenRecord: Boolean,
+ winscopeTracing: Boolean
+ ): Intent =
+ Intent(context, IssueRecordingService::class.java)
+ .setAction(ACTION_START)
+ .putExtra(EXTRA_SCREEN_RECORD, screenRecord)
+ .putExtra(EXTRA_WINSCOPE_TRACING, winscopeTracing)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index 1c07d00..ff18a11 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -17,8 +17,6 @@
package com.android.systemui.recordissue
import android.annotation.SuppressLint
-import android.app.BroadcastOptions
-import android.app.PendingIntent
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
@@ -43,8 +41,6 @@
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate
import com.android.systemui.qs.tiles.RecordIssueTile
import com.android.systemui.res.R
-import com.android.systemui.screenrecord.RecordingService
-import com.android.systemui.settings.UserContextProvider
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -52,12 +48,12 @@
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.util.concurrent.Executor
+import java.util.function.Consumer
class RecordIssueDialogDelegate
@AssistedInject
constructor(
private val factory: SystemUIDialog.Factory,
- private val userContextProvider: UserContextProvider,
private val userTracker: UserTracker,
private val flags: FeatureFlagsClassic,
@Background private val bgExecutor: Executor,
@@ -66,14 +62,14 @@
private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
private val userFileManager: UserFileManager,
private val screenCaptureDisabledDialogDelegate: ScreenCaptureDisabledDialogDelegate,
- @Assisted private val onStarted: Runnable,
+ @Assisted private val onStarted: Consumer<IssueRecordingConfig>,
) : SystemUIDialog.Delegate {
/** To inject dependencies and allow for easier testing */
@AssistedFactory
interface Factory {
/** Create a dialog object */
- fun create(onStarted: Runnable): RecordIssueDialogDelegate
+ fun create(onStarted: Consumer<IssueRecordingConfig>): RecordIssueDialogDelegate
}
@SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var screenRecordSwitch: Switch
@@ -87,10 +83,12 @@
setIcon(R.drawable.qs_record_issue_icon_off)
setNegativeButton(R.string.cancel) { _, _ -> dismiss() }
setPositiveButton(R.string.qs_record_issue_start) { _, _ ->
- onStarted.run()
- if (screenRecordSwitch.isChecked) {
- requestScreenCapture()
- }
+ onStarted.accept(
+ IssueRecordingConfig(
+ screenRecordSwitch.isChecked,
+ true /* TODO: Base this on issueType selected */
+ )
+ )
dismiss()
}
}
@@ -177,13 +175,4 @@
show()
}
}
-
- private fun requestScreenCapture() =
- PendingIntent.getForegroundService(
- userContextProvider.userContext,
- RecordingService.REQUEST_CODE,
- IssueRecordingService.getStartIntent(userContextProvider.userContext),
- PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
- )
- .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle())
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/PanelExpansionInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/PanelExpansionInteractor.kt
deleted file mode 100644
index 3a5ea5c..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/PanelExpansionInteractor.kt
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.scene.domain.interactor
-
-import com.android.compose.animation.scene.ObservableTransitionState
-import com.android.compose.animation.scene.SceneKey
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.data.repository.ShadeRepository
-import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
-
-@SysUISingleton
-class PanelExpansionInteractor
-@Inject
-constructor(
- sceneInteractor: SceneInteractor,
- shadeRepository: ShadeRepository,
-) {
-
- /**
- * The amount by which the "panel" has been expanded (`0` when fully collapsed, `1` when fully
- * expanded).
- *
- * This is a legacy concept from the time when the "panel" included the notification/QS shades
- * as well as the keyguard (lockscreen and bouncer). This value is meant only for
- * backwards-compatibility and should not be consumed by newer code.
- */
- @Deprecated("Use SceneInteractor.currentScene instead.")
- val legacyPanelExpansion: Flow<Float> =
- if (SceneContainerFlag.isEnabled) {
- sceneInteractor.transitionState.flatMapLatest { state ->
- when (state) {
- is ObservableTransitionState.Idle ->
- flowOf(
- if (state.scene != Scenes.Gone) {
- // When resting on a non-Gone scene, the panel is fully expanded.
- 1f
- } else {
- // When resting on the Gone scene, the panel is considered fully
- // collapsed.
- 0f
- }
- )
- is ObservableTransitionState.Transition ->
- when {
- state.fromScene == Scenes.Gone ->
- if (state.toScene.isExpandable()) {
- // Moving from Gone to a scene that can animate-expand has a
- // panel
- // expansion
- // that tracks with the transition.
- state.progress
- } else {
- // Moving from Gone to a scene that doesn't animate-expand
- // immediately makes
- // the panel fully expanded.
- flowOf(1f)
- }
- state.toScene == Scenes.Gone ->
- if (state.fromScene.isExpandable()) {
- // Moving to Gone from a scene that can animate-expand has a
- // panel
- // expansion
- // that tracks with the transition.
- state.progress.map { 1 - it }
- } else {
- // Moving to Gone from a scene that doesn't animate-expand
- // immediately makes
- // the panel fully collapsed.
- flowOf(0f)
- }
- else -> flowOf(1f)
- }
- }
- }
- } else {
- shadeRepository.legacyShadeExpansion
- }
-
- private fun SceneKey.isExpandable(): Boolean {
- return this == Scenes.Shade || this == Scenes.QuickSettings
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
new file mode 100644
index 0000000..451fd67
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.ui.viewmodel
+
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+@SysUISingleton
+class GoneSceneViewModel
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ shadeInteractor: ShadeInteractor,
+) {
+ val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ shadeInteractor.shadeMode
+ .map { shadeMode -> destinationScenes(shadeMode = shadeMode) }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = destinationScenes(shadeMode = shadeInteractor.shadeMode.value)
+ )
+
+ private fun destinationScenes(shadeMode: ShadeMode): Map<UserAction, UserActionResult> {
+ return buildMap {
+ if (shadeMode == ShadeMode.Single) {
+ this[
+ Swipe(
+ pointerCount = 2,
+ fromSource = Edge.Top,
+ direction = SwipeDirection.Down,
+ )] = UserActionResult(Scenes.QuickSettings)
+ }
+
+ this[Swipe(direction = SwipeDirection.Down)] = UserActionResult(Scenes.Shade)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index b355d2d..ac94f39 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -60,25 +60,27 @@
public static final int REQUEST_CODE = 2;
private static final int USER_ID_NOT_SPECIFIED = -1;
- private static final int NOTIF_BASE_ID = 4273;
+ protected static final int NOTIF_BASE_ID = 4273;
private static final String TAG = "RecordingService";
private static final String CHANNEL_ID = "screen_record";
private static final String GROUP_KEY = "screen_record_saved";
private static final String EXTRA_RESULT_CODE = "extra_resultCode";
- private static final String EXTRA_PATH = "extra_path";
+ protected static final String EXTRA_PATH = "extra_path";
private static final String EXTRA_AUDIO_SOURCE = "extra_useAudio";
private static final String EXTRA_SHOW_TAPS = "extra_showTaps";
private static final String EXTRA_CAPTURE_TARGET = "extra_captureTarget";
protected static final String ACTION_START = "com.android.systemui.screenrecord.START";
+ protected static final String ACTION_SHOW_START_NOTIF =
+ "com.android.systemui.screenrecord.START_NOTIF";
protected static final String ACTION_STOP = "com.android.systemui.screenrecord.STOP";
- private static final String ACTION_STOP_NOTIF =
+ protected static final String ACTION_STOP_NOTIF =
"com.android.systemui.screenrecord.STOP_FROM_NOTIF";
- private static final String ACTION_SHARE = "com.android.systemui.screenrecord.SHARE";
+ protected static final String ACTION_SHARE = "com.android.systemui.screenrecord.SHARE";
private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
private final RecordingController mController;
- private final KeyguardDismissUtil mKeyguardDismissUtil;
+ protected final KeyguardDismissUtil mKeyguardDismissUtil;
private final Handler mMainHandler;
private ScreenRecordingAudioSource mAudioSource;
private boolean mShowTaps;
@@ -86,9 +88,9 @@
private ScreenMediaRecorder mRecorder;
private final Executor mLongExecutor;
private final UiEventLogger mUiEventLogger;
- private final NotificationManager mNotificationManager;
- private final UserContextProvider mUserContextTracker;
- private int mNotificationId = NOTIF_BASE_ID;
+ protected final NotificationManager mNotificationManager;
+ protected final UserContextProvider mUserContextTracker;
+ protected int mNotificationId = NOTIF_BASE_ID;
private RecordingServiceStrings mStrings;
@Inject
@@ -185,7 +187,10 @@
return Service.START_NOT_STICKY;
}
break;
-
+ case ACTION_SHOW_START_NOTIF:
+ createRecordingNotification();
+ mUiEventLogger.log(Events.ScreenRecordEvent.SCREEN_RECORD_START);
+ break;
case ACTION_STOP_NOTIF:
case ACTION_STOP:
// only difference for actions is the log event
@@ -232,6 +237,7 @@
super.onCreate();
}
+ @Nullable
@VisibleForTesting
protected ScreenMediaRecorder getRecorder() {
return mRecorder;
@@ -337,8 +343,9 @@
}
@VisibleForTesting
- protected Notification createSaveNotification(ScreenMediaRecorder.SavedRecording recording) {
- Uri uri = recording.getUri();
+ protected Notification createSaveNotification(
+ @Nullable ScreenMediaRecorder.SavedRecording recording) {
+ Uri uri = recording != null ? recording.getUri() : null;
Intent viewIntent = new Intent(Intent.ACTION_VIEW)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION)
.setDataAndType(uri, "video/mp4");
@@ -349,7 +356,7 @@
PendingIntent.getService(
this,
REQUEST_CODE,
- getShareIntent(this, uri.toString()),
+ getShareIntent(this, uri != null ? uri.toString() : null),
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
.build();
@@ -371,9 +378,10 @@
.addExtras(extras);
// Add thumbnail if available
- if (recording.getThumbnail() != null) {
+ Icon thumbnail = recording != null ? recording.getThumbnail() : null;
+ if (thumbnail != null) {
Notification.BigPictureStyle pictureStyle = new Notification.BigPictureStyle()
- .bigPicture(recording.getThumbnail())
+ .bigPicture(thumbnail)
.showBigPictureWhenCollapsed(true);
builder.setStyle(pictureStyle);
}
@@ -408,27 +416,29 @@
}
Log.d(getTag(), "notifying for user " + userId);
setTapsVisible(mOriginalShowTaps);
- if (getRecorder() != null) {
- try {
+ try {
+ if (getRecorder() != null) {
getRecorder().end();
- saveRecording(userId);
- } catch (RuntimeException exception) {
+ }
+ saveRecording(userId);
+ } catch (RuntimeException exception) {
+ if (getRecorder() != null) {
// RuntimeException could happen if the recording stopped immediately after starting
// let's release the recorder and delete all temporary files in this case
getRecorder().release();
- showErrorToast(R.string.screenrecord_start_error);
- Log.e(getTag(), "stopRecording called, but there was an error when ending"
- + "recording");
- exception.printStackTrace();
- createErrorNotification();
- } catch (Throwable throwable) {
+ }
+ showErrorToast(R.string.screenrecord_start_error);
+ Log.e(getTag(), "stopRecording called, but there was an error when ending"
+ + "recording");
+ exception.printStackTrace();
+ createErrorNotification();
+ } catch (Throwable throwable) {
+ if (getRecorder() != null) {
// Something unexpected happen, SystemUI will crash but let's delete
// the temporary files anyway
getRecorder().release();
- throw new RuntimeException(throwable);
}
- } else {
- Log.e(getTag(), "stopRecording called, but recorder was null");
+ throw new RuntimeException(throwable);
}
updateState(false);
stopForeground(STOP_FOREGROUND_DETACH);
@@ -443,7 +453,8 @@
mLongExecutor.execute(() -> {
try {
Log.d(getTag(), "saving recording");
- Notification notification = createSaveNotification(getRecorder().save());
+ Notification notification = createSaveNotification(
+ getRecorder() != null ? getRecorder().save() : null);
postGroupNotification(currentUser);
mNotificationManager.notifyAsUser(null, mNotificationId, notification,
currentUser);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
index b42cc98..c4287ca 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
@@ -21,7 +21,9 @@
import android.annotation.IntRange;
import android.content.ContentProvider;
import android.content.ContentResolver;
+import android.content.ContentUris;
import android.content.ContentValues;
+import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.net.Uri;
@@ -50,6 +52,7 @@
import java.io.OutputStream;
import java.time.Duration;
import java.time.Instant;
+import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
@@ -145,32 +148,84 @@
}
/**
- * Export the image using the given executor.
+ * Export the image using the given executor with an auto-generated file name based on display
+ * id.
*
- * @param executor the thread for execution
- * @param bitmap the bitmap to export
+ * @param executor the thread for execution
+ * @param bitmap the bitmap to export
* @param displayId the display id the bitmap comes from.
* @return a listenable future result
*/
public ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap,
UserHandle owner, int displayId) {
- return export(executor, requestId, bitmap, ZonedDateTime.now(), owner, displayId);
+ ZonedDateTime captureTime = ZonedDateTime.now(ZoneId.systemDefault());
+ return export(executor,
+ new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat,
+ mQuality, /* publish */ true, owner, mFlags,
+ createFilename(captureTime, mCompressFormat, displayId)));
+ }
+
+ /**
+ * Export the image using the given executor with a specified file name.
+ *
+ * @param executor the thread for execution
+ * @param bitmap the bitmap to export
+ * @param format the compress format of {@code bitmap} e.g. {@link CompressFormat.PNG}
+ * @param fileName a specified name for the exported file. No need to include file extension in
+ * file name. The extension will be internally appended based on
+ * {@code format}
+ * @return a listenable future result
+ */
+ public ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap,
+ CompressFormat format, UserHandle owner, String fileName) {
+ return export(executor,
+ new Task(mResolver,
+ requestId,
+ bitmap,
+ ZonedDateTime.now(ZoneId.systemDefault()),
+ format,
+ mQuality, /* publish */ true, owner, mFlags,
+ createSystemFileDisplayName(fileName, format),
+ true /* allowOverwrite */));
}
/**
* Export the image to MediaStore and publish.
*
* @param executor the thread for execution
- * @param bitmap the bitmap to export
- *
+ * @param bitmap the bitmap to export
* @return a listenable future result
*/
ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap,
ZonedDateTime captureTime, UserHandle owner, int displayId) {
+ return export(executor, new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat,
+ mQuality, /* publish */ true, owner, mFlags,
+ createFilename(captureTime, mCompressFormat, displayId)));
+ }
- final Task task = new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat,
- mQuality, /* publish */ true, owner, mFlags, displayId);
+ /**
+ * Export the image to MediaStore and publish.
+ *
+ * @param executor the thread for execution
+ * @param bitmap the bitmap to export
+ * @return a listenable future result
+ */
+ ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap,
+ ZonedDateTime captureTime, UserHandle owner, String fileName) {
+ return export(executor, new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat,
+ mQuality, /* publish */ true, owner, mFlags,
+ createSystemFileDisplayName(fileName, mCompressFormat)));
+ }
+ /**
+ * Export the image to MediaStore and publish.
+ *
+ * @param executor the thread for execution
+ * @param task the exporting image {@link Task}.
+ *
+ * @return a listenable future result
+ */
+ private ListenableFuture<Result> export(Executor executor, Task task) {
return CallbackToFutureAdapter.getFuture(
(completer) -> {
executor.execute(() -> {
@@ -220,9 +275,25 @@
private final boolean mPublish;
private final FeatureFlags mFlags;
+ /**
+ * This variable specifies the behavior when a file to be exported has a same name and
+ * format as one of the file on disk. If this is set to true, the new file overwrite the
+ * old file; otherwise, the system adds a number to the end of the newly exported file. For
+ * example, if the file is screenshot.png, the newly exported file's display name will be
+ * screenshot(1).png.
+ */
+ private final boolean mAllowOverwrite;
+
Task(ContentResolver resolver, UUID requestId, Bitmap bitmap, ZonedDateTime captureTime,
CompressFormat format, int quality, boolean publish, UserHandle owner,
- FeatureFlags flags, int displayId) {
+ FeatureFlags flags, String fileName) {
+ this(resolver, requestId, bitmap, captureTime, format, quality, publish, owner, flags,
+ fileName, false /* allowOverwrite */);
+ }
+
+ Task(ContentResolver resolver, UUID requestId, Bitmap bitmap, ZonedDateTime captureTime,
+ CompressFormat format, int quality, boolean publish, UserHandle owner,
+ FeatureFlags flags, String fileName, boolean allowOverwrite) {
mResolver = resolver;
mRequestId = requestId;
mBitmap = bitmap;
@@ -230,9 +301,10 @@
mFormat = format;
mQuality = quality;
mOwner = owner;
- mFileName = createFilename(mCaptureTime, mFormat, displayId);
+ mFileName = fileName;
mPublish = publish;
mFlags = flags;
+ mAllowOverwrite = allowOverwrite;
}
public Result execute() throws ImageExportException, InterruptedException {
@@ -246,7 +318,8 @@
start = Instant.now();
}
- uri = createEntry(mResolver, mFormat, mCaptureTime, mFileName, mOwner, mFlags);
+ uri = createEntry(mResolver, mFormat, mCaptureTime, mFileName, mOwner, mFlags,
+ mAllowOverwrite);
throwIfInterrupted();
writeImage(mResolver, mBitmap, mFormat, mQuality, uri);
@@ -290,21 +363,51 @@
}
private static Uri createEntry(ContentResolver resolver, CompressFormat format,
- ZonedDateTime time, String fileName, UserHandle owner, FeatureFlags flags)
- throws ImageExportException {
+ ZonedDateTime time, String fileName, UserHandle owner, FeatureFlags flags,
+ boolean allowOverwrite) throws ImageExportException {
Trace.beginSection("ImageExporter_createEntry");
try {
final ContentValues values = createMetadata(time, format, fileName);
Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
Uri uriWithUserId = ContentProvider.maybeAddUserId(baseUri, owner.getIdentifier());
+ Uri resultUri = null;
- Uri uri = resolver.insert(uriWithUserId, values);
- if (uri == null) {
+ if (allowOverwrite) {
+ // Query to check if there is existing file with the same name and format.
+ Cursor cursor = resolver.query(
+ baseUri,
+ null,
+ MediaStore.MediaColumns.DISPLAY_NAME + "=? AND "
+ + MediaStore.MediaColumns.MIME_TYPE + "=?",
+ new String[]{fileName, getMimeType(format)},
+ null /* CancellationSignal */);
+ if (cursor != null) {
+ if (cursor.moveToFirst()) {
+ // If there is existing file, update the meta-data of its entry. The Entry's
+ // corresponding uri is composed of volume base-uri(or with user-id) and
+ // its row's unique ID.
+ int idIndex = cursor.getColumnIndex(MediaStore.MediaColumns._ID);
+ resultUri = ContentUris.withAppendedId(uriWithUserId,
+ cursor.getLong(idIndex));
+ resolver.update(resultUri, values, null);
+ Log.d(TAG, "Updated existing URI: " + resultUri);
+ }
+ cursor.close();
+ }
+ }
+
+ if (resultUri == null) {
+ // If file overwriting is disabled or there is no existing file to overwrite, create
+ // and insert a new entry.
+ resultUri = resolver.insert(uriWithUserId, values);
+ Log.d(TAG, "Inserted new URI: " + resultUri);
+ }
+
+ if (resultUri == null) {
throw new ImageExportException(RESOLVER_INSERT_RETURNED_NULL);
}
- Log.d(TAG, "Inserted new URI: " + uri);
- return uri;
+ return resultUri;
} finally {
Trace.endSection();
}
@@ -383,6 +486,11 @@
fileExtension(format));
}
+ @VisibleForTesting
+ static String createSystemFileDisplayName(String originalDisplayName, CompressFormat format) {
+ return originalDisplayName + "." + fileExtension(format);
+ }
+
static ContentValues createMetadata(ZonedDateTime captureTime, CompressFormat format,
String fileName) {
ContentValues values = new ContentValues();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
index 2294fc0..1f6d212 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
@@ -21,59 +21,62 @@
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Rect
-import android.graphics.drawable.Drawable
-import android.view.Display
+import android.util.Log
+import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.ScrollCaptureResponse
import android.view.View
import android.view.ViewTreeObserver
import android.view.WindowInsets
+import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
+import androidx.appcompat.content.res.AppCompatResources
import com.android.internal.logging.UiEventLogger
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.res.R
+import com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
/**
* Legacy implementation of screenshot view methods. Just proxies the calls down into the original
* ScreenshotView.
*/
-class LegacyScreenshotViewProxy(context: Context) : ScreenshotViewProxy {
+class LegacyScreenshotViewProxy
+@AssistedInject
+constructor(
+ private val logger: UiEventLogger,
+ flags: FeatureFlags,
+ @Assisted private val context: Context,
+ @Assisted private val displayId: Int
+) : ScreenshotViewProxy {
override val view: ScreenshotView =
LayoutInflater.from(context).inflate(R.layout.screenshot, null) as ScreenshotView
- override val internalInsetsListener: ViewTreeObserver.OnComputeInternalInsetsListener
override val screenshotPreview: View
-
- override var defaultDisplay: Int = Display.DEFAULT_DISPLAY
- set(value) {
- view.setDefaultDisplay(value)
- }
- override var defaultTimeoutMillis: Long = 6000
- set(value) {
- view.setDefaultTimeoutMillis(value)
- }
- override var onKeyListener: View.OnKeyListener? = null
- set(value) {
- view.setOnKeyListener(value)
- }
- override var flags: FeatureFlags? = null
- set(value) {
- view.setFlags(value)
- }
override var packageName: String = ""
set(value) {
+ field = value
view.setPackageName(value)
}
- override var logger: UiEventLogger? = null
- set(value) {
- view.setUiEventLogger(value)
- }
override var callbacks: ScreenshotView.ScreenshotViewCallback? = null
set(value) {
+ field = value
view.setCallbacks(value)
}
override var screenshot: ScreenshotData? = null
set(value) {
- view.setScreenshot(value)
+ field = value
+ value?.let {
+ val badgeBg =
+ AppCompatResources.getDrawable(context, R.drawable.overlay_badge_background)
+ val user = it.userHandle
+ if (badgeBg != null && user != null) {
+ view.badgeScreenshot(context.packageManager.getUserBadgedIcon(badgeBg, user))
+ }
+ view.setScreenshot(it)
+ }
}
override val isAttachedToWindow
@@ -84,7 +87,15 @@
get() = view.isPendingSharedTransition
init {
- internalInsetsListener = view
+ view.setUiEventLogger(logger)
+ view.setDefaultDisplay(displayId)
+ view.setFlags(flags)
+ addPredictiveBackListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
+ setOnKeyListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
+ if (LogConfig.DEBUG_WINDOW) {
+ Log.d(TAG, "adding OnComputeInternalInsetsListener")
+ }
+ view.viewTreeObserver.addOnComputeInternalInsetsListener(view)
screenshotPreview = view.screenshotPreview
}
@@ -92,8 +103,6 @@
override fun updateInsets(insets: WindowInsets) = view.updateInsets(insets)
override fun updateOrientation(insets: WindowInsets) = view.updateOrientation(insets)
- override fun badgeScreenshot(userBadgedIcon: Drawable) = view.badgeScreenshot(userBadgedIcon)
-
override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator =
view.createScreenshotDropInAnimation(screenRect, showFlash)
@@ -103,7 +112,20 @@
override fun setChipIntents(imageData: ScreenshotController.SavedImageData) =
view.setChipIntents(imageData)
- override fun animateDismissal() = view.animateDismissal()
+ override fun requestDismissal(event: ScreenshotEvent) {
+ if (DEBUG_DISMISS) {
+ Log.d(TAG, "screenshot dismissal requested")
+ }
+ // If we're already animating out, don't restart the animation
+ if (view.isDismissing) {
+ if (DEBUG_DISMISS) {
+ Log.v(TAG, "Already dismissing, ignoring duplicate command $event")
+ }
+ return
+ }
+ logger.log(event, 0, packageName)
+ view.animateDismissal()
+ }
override fun showScrollChip(packageName: String, onClick: Runnable) =
view.showScrollChip(packageName, onClick)
@@ -114,14 +136,17 @@
response: ScrollCaptureResponse,
screenBitmap: Bitmap,
newScreenshot: Bitmap,
- screenshotTakenInPortrait: Boolean
- ) =
+ screenshotTakenInPortrait: Boolean,
+ onTransitionPrepared: Runnable,
+ ) {
view.prepareScrollingTransition(
response,
screenBitmap,
newScreenshot,
screenshotTakenInPortrait
)
+ view.post { onTransitionPrepared.run() }
+ }
override fun startLongScreenshotTransition(
transitionDestination: Rect,
@@ -139,21 +164,76 @@
override fun announceForAccessibility(string: String) = view.announceForAccessibility(string)
- override fun addOnAttachStateChangeListener(listener: View.OnAttachStateChangeListener) =
- view.addOnAttachStateChangeListener(listener)
-
- override fun findOnBackInvokedDispatcher(): OnBackInvokedDispatcher? =
- view.findOnBackInvokedDispatcher()
-
- override fun getViewTreeObserver(): ViewTreeObserver = view.viewTreeObserver
-
- override fun post(runnable: Runnable) {
- view.post(runnable)
+ override fun prepareEntranceAnimation(runnable: Runnable) {
+ view.viewTreeObserver.addOnPreDrawListener(
+ object : ViewTreeObserver.OnPreDrawListener {
+ override fun onPreDraw(): Boolean {
+ if (LogConfig.DEBUG_WINDOW) {
+ Log.d(TAG, "onPreDraw: startAnimation")
+ }
+ view.viewTreeObserver.removeOnPreDrawListener(this)
+ runnable.run()
+ return true
+ }
+ }
+ )
}
- class Factory : ScreenshotViewProxy.Factory {
- override fun getProxy(context: Context): ScreenshotViewProxy {
- return LegacyScreenshotViewProxy(context)
+ private fun addPredictiveBackListener(onDismissRequested: (ScreenshotEvent) -> Unit) {
+ val onBackInvokedCallback = OnBackInvokedCallback {
+ if (LogConfig.DEBUG_INPUT) {
+ Log.d(TAG, "Predictive Back callback dispatched")
+ }
+ onDismissRequested.invoke(SCREENSHOT_DISMISSED_OTHER)
}
+ view.addOnAttachStateChangeListener(
+ object : View.OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(v: View) {
+ if (LogConfig.DEBUG_INPUT) {
+ Log.d(TAG, "Registering Predictive Back callback")
+ }
+ view
+ .findOnBackInvokedDispatcher()
+ ?.registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+ onBackInvokedCallback
+ )
+ }
+
+ override fun onViewDetachedFromWindow(view: View) {
+ if (LogConfig.DEBUG_INPUT) {
+ Log.d(TAG, "Unregistering Predictive Back callback")
+ }
+ view
+ .findOnBackInvokedDispatcher()
+ ?.unregisterOnBackInvokedCallback(onBackInvokedCallback)
+ }
+ }
+ )
+ }
+ private fun setOnKeyListener(onDismissRequested: (ScreenshotEvent) -> Unit) {
+ view.setOnKeyListener(
+ object : View.OnKeyListener {
+ override fun onKey(view: View, keyCode: Int, event: KeyEvent): Boolean {
+ if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
+ if (LogConfig.DEBUG_INPUT) {
+ Log.d(TAG, "onKeyEvent: $keyCode")
+ }
+ onDismissRequested.invoke(SCREENSHOT_DISMISSED_OTHER)
+ return true
+ }
+ return false
+ }
+ }
+ )
+ }
+
+ @AssistedFactory
+ interface Factory : ScreenshotViewProxy.Factory {
+ override fun getProxy(context: Context, displayId: Int): LegacyScreenshotViewProxy
+ }
+
+ companion object {
+ private const val TAG = "LegacyScreenshotViewProxy"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 13448d2..198a29c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -21,7 +21,6 @@
import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
-import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT;
import static com.android.systemui.screenshot.LogConfig.DEBUG_UI;
import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW;
@@ -64,7 +63,6 @@
import android.view.Display;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
-import android.view.KeyEvent;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.ScrollCaptureResponse;
@@ -76,8 +74,6 @@
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
-import android.window.OnBackInvokedCallback;
-import android.window.OnBackInvokedDispatcher;
import android.window.WindowContext;
import com.android.internal.app.ChooserActivity;
@@ -232,7 +228,7 @@
// From WizardManagerHelper.java
private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";
- private static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
+ static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
private final WindowContext mContext;
private final FeatureFlags mFlags;
@@ -265,13 +261,6 @@
private final UserManager mUserManager;
private final AssistContentRequester mAssistContentRequester;
- private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
- if (DEBUG_INPUT) {
- Log.d(TAG, "Predictive Back callback dispatched");
- }
- respondToKeyDismissal();
- };
-
private final MessageContainerController mMessageContainerController;
private Bitmap mScreenBitmap;
private SaveImageInBackgroundTask mSaveInBgTask;
@@ -342,12 +331,7 @@
mScreenshotHandler = timeoutHandler;
mScreenshotHandler.setDefaultTimeoutMillis(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS);
- mScreenshotHandler.setOnTimeoutRunnable(() -> {
- if (DEBUG_UI) {
- Log.d(TAG, "Corner timeout hit");
- }
- dismissScreenshot(SCREENSHOT_INTERACTION_TIMEOUT);
- });
+
mDisplayId = displayId;
mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
@@ -360,7 +344,14 @@
mMessageContainerController = messageContainerController;
mAssistContentRequester = assistContentRequester;
- mViewProxy = viewProxyFactory.getProxy(mContext);
+ mViewProxy = viewProxyFactory.getProxy(mContext, mDisplayId);
+
+ mScreenshotHandler.setOnTimeoutRunnable(() -> {
+ if (DEBUG_UI) {
+ Log.d(TAG, "Corner timeout hit");
+ }
+ mViewProxy.requestDismissal(SCREENSHOT_INTERACTION_TIMEOUT);
+ });
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
@@ -385,7 +376,7 @@
@Override
public void onReceive(Context context, Intent intent) {
if (ClipboardOverlayController.COPY_OVERLAY_ACTION.equals(intent.getAction())) {
- dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
+ mViewProxy.requestDismissal(SCREENSHOT_DISMISSED_OTHER);
}
}
};
@@ -469,7 +460,7 @@
attachWindow();
- boolean showFlash = true;
+ boolean showFlash;
if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) {
if (screenshot.getScreenBounds() != null
&& aspectRatiosMatch(screenshot.getBitmap(), screenshot.getInsets(),
@@ -481,15 +472,14 @@
screenshot.setScreenBounds(new Rect(0, 0, screenshot.getBitmap().getWidth(),
screenshot.getBitmap().getHeight()));
}
+ } else {
+ showFlash = true;
}
- prepareAnimation(screenshot.getScreenBounds(), showFlash, () -> {
- mMessageContainerController.onScreenshotTaken(screenshot);
- });
+ mViewProxy.prepareEntranceAnimation(
+ () -> startAnimation(screenshot.getScreenBounds(), showFlash,
+ () -> mMessageContainerController.onScreenshotTaken(screenshot)));
- mViewProxy.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
- mContext.getDrawable(R.drawable.overlay_badge_background),
- screenshot.getUserHandle()));
mViewProxy.setScreenshot(screenshot);
// ignore system bar insets for the purpose of window layout
@@ -534,22 +524,11 @@
}
/**
- * Clears current screenshot
+ * Requests the view to dismiss the current screenshot (may be ignored, if screenshot is already
+ * being dismissed)
*/
- void dismissScreenshot(ScreenshotEvent event) {
- if (DEBUG_DISMISS) {
- Log.d(TAG, "dismissScreenshot");
- }
- // If we're already animating out, don't restart the animation
- if (mViewProxy.isDismissing()) {
- if (DEBUG_DISMISS) {
- Log.v(TAG, "Already dismissing, ignoring duplicate command");
- }
- return;
- }
- mUiEventLogger.log(event, 0, mPackageName);
- mScreenshotHandler.cancelTimeout();
- mViewProxy.animateDismissal();
+ void requestDismissal(ScreenshotEvent event) {
+ mViewProxy.requestDismissal(event);
}
boolean isPendingSharedTransition() {
@@ -581,10 +560,6 @@
mScreenshotSoundController.releaseScreenshotSoundAsync();
}
- private void respondToKeyDismissal() {
- dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
- }
-
/**
* Update resources on configuration change. Reinflate for theme/color changes.
*/
@@ -594,27 +569,6 @@
}
mMessageContainerController.setView(mViewProxy.getView());
- mViewProxy.addOnAttachStateChangeListener(
- new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(@NonNull View v) {
- if (DEBUG_INPUT) {
- Log.d(TAG, "Registering Predictive Back callback");
- }
- mViewProxy.findOnBackInvokedDispatcher().registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback);
- }
-
- @Override
- public void onViewDetachedFromWindow(@NonNull View v) {
- if (DEBUG_INPUT) {
- Log.d(TAG, "Unregistering Predictive Back callback");
- }
- mViewProxy.findOnBackInvokedDispatcher()
- .unregisterOnBackInvokedCallback(mOnBackInvokedCallback);
- }
- });
- mViewProxy.setLogger(mUiEventLogger);
mViewProxy.setCallbacks(new ScreenshotView.ScreenshotViewCallback() {
@Override
public void onUserInteraction() {
@@ -641,48 +595,13 @@
setWindowFocusable(false);
}
});
- mViewProxy.setFlags(mFlags);
- mViewProxy.setDefaultDisplay(mDisplayId);
- mViewProxy.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis());
- mViewProxy.setOnKeyListener((v, keyCode, event) -> {
- if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
- if (DEBUG_INPUT) {
- Log.d(TAG, "onKeyEvent: " + keyCode);
- }
- respondToKeyDismissal();
- return true;
- }
- return false;
- });
-
- if (DEBUG_WINDOW) {
- Log.d(TAG, "adding OnComputeInternalInsetsListener");
- }
- mViewProxy.getViewTreeObserver().addOnComputeInternalInsetsListener(
- mViewProxy.getInternalInsetsListener());
if (DEBUG_WINDOW) {
Log.d(TAG, "setContentView: " + mViewProxy.getView());
}
setContentView(mViewProxy.getView());
}
- private void prepareAnimation(Rect screenRect, boolean showFlash,
- Runnable onAnimationComplete) {
- mViewProxy.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- if (DEBUG_WINDOW) {
- Log.d(TAG, "onPreDraw: startAnimation");
- }
- mViewProxy.getViewTreeObserver().removeOnPreDrawListener(this);
- startAnimation(screenRect, showFlash, onAnimationComplete);
- return true;
- }
- });
- }
-
private void enqueueScrollCaptureRequest(UserHandle owner) {
// Wait until this window is attached to request because it is
// the reference used to locate the target window (below).
@@ -767,10 +686,14 @@
Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplayId,
new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));
- mViewProxy.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
- mScreenshotTakenInPortrait);
- // delay starting scroll capture to make sure the scrim is up before the app moves
- mViewProxy.post(() -> runBatchScrollCapture(response, owner));
+ if (newScreenshot != null) {
+ // delay starting scroll capture to make sure scrim is up before the app moves
+ mViewProxy.prepareScrollingTransition(
+ response, mScreenBitmap, newScreenshot, mScreenshotTakenInPortrait,
+ () -> runBatchScrollCapture(response, owner));
+ } else {
+ Log.wtf(TAG, "failed to capture current screenshot for scroll transition");
+ }
});
} catch (InterruptedException | ExecutionException e) {
Log.e(TAG, "requestScrollCapture failed", e);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 8a8766d..1c5a8a1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -26,6 +26,7 @@
import static com.android.systemui.screenshot.LogConfig.DEBUG_UI;
import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW;
import static com.android.systemui.screenshot.LogConfig.logTag;
+import static com.android.systemui.screenshot.ScreenshotController.SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS;
import static java.util.Objects.requireNonNull;
@@ -33,6 +34,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.BroadcastOptions;
import android.app.Notification;
@@ -168,7 +170,6 @@
private ScreenshotData mScreenshotData;
private final InteractionJankMonitor mInteractionJankMonitor;
- private long mDefaultTimeoutOfTimeoutHandler;
private FeatureFlags mFlags;
private final Bundle mInteractiveBroadcastOption;
@@ -244,10 +245,6 @@
return InteractionJankMonitor.getInstance();
}
- void setDefaultTimeoutMillis(long timeout) {
- mDefaultTimeoutOfTimeoutHandler = timeout;
- }
-
public void hideScrollChip() {
mScrollChip.setVisibility(View.GONE);
}
@@ -755,7 +752,7 @@
InteractionJankMonitor.Configuration.Builder.withView(
CUJ_TAKE_SCREENSHOT, mScreenshotStatic)
.setTag("Actions")
- .setTimeout(mDefaultTimeoutOfTimeoutHandler);
+ .setTimeout(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS);
mInteractionJankMonitor.begin(builder);
}
});
@@ -781,7 +778,7 @@
return animator;
}
- void badgeScreenshot(Drawable badge) {
+ void badgeScreenshot(@Nullable Drawable badge) {
mScreenshotBadge.setImageDrawable(badge);
mScreenshotBadge.setVisibility(badge != null ? View.VISIBLE : View.GONE);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
index 0064521..182b889 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
@@ -21,29 +21,17 @@
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Rect
-import android.graphics.drawable.Drawable
import android.view.ScrollCaptureResponse
import android.view.View
-import android.view.View.OnKeyListener
import android.view.ViewGroup
-import android.view.ViewTreeObserver
import android.view.WindowInsets
-import android.window.OnBackInvokedDispatcher
-import com.android.internal.logging.UiEventLogger
-import com.android.systemui.flags.FeatureFlags
/** Abstraction of the surface between ScreenshotController and ScreenshotView */
interface ScreenshotViewProxy {
val view: ViewGroup
- val internalInsetsListener: ViewTreeObserver.OnComputeInternalInsetsListener
val screenshotPreview: View
- var defaultDisplay: Int
- var defaultTimeoutMillis: Long
- var onKeyListener: OnKeyListener?
- var flags: FeatureFlags?
var packageName: String
- var logger: UiEventLogger?
var callbacks: ScreenshotView.ScreenshotViewCallback?
var screenshot: ScreenshotData?
@@ -54,11 +42,10 @@
fun reset()
fun updateInsets(insets: WindowInsets)
fun updateOrientation(insets: WindowInsets)
- fun badgeScreenshot(userBadgedIcon: Drawable)
fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator
fun addQuickShareChip(quickShareAction: Notification.Action)
fun setChipIntents(imageData: ScreenshotController.SavedImageData)
- fun animateDismissal()
+ fun requestDismissal(event: ScreenshotEvent)
fun showScrollChip(packageName: String, onClick: Runnable)
fun hideScrollChip()
@@ -66,7 +53,8 @@
response: ScrollCaptureResponse,
screenBitmap: Bitmap,
newScreenshot: Bitmap,
- screenshotTakenInPortrait: Boolean
+ screenshotTakenInPortrait: Boolean,
+ onTransitionPrepared: Runnable,
)
fun startLongScreenshotTransition(
transitionDestination: Rect,
@@ -78,12 +66,9 @@
fun stopInputListening()
fun requestFocus()
fun announceForAccessibility(string: String)
- fun addOnAttachStateChangeListener(listener: View.OnAttachStateChangeListener)
- fun findOnBackInvokedDispatcher(): OnBackInvokedDispatcher?
- fun getViewTreeObserver(): ViewTreeObserver
- fun post(runnable: Runnable)
+ fun prepareEntranceAnimation(runnable: Runnable)
interface Factory {
- fun getProxy(context: Context): ScreenshotViewProxy
+ fun getProxy(context: Context, displayId: Int): ScreenshotViewProxy
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index e464fd0..bc33755 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -136,7 +136,7 @@
fun onCloseSystemDialogsReceived() {
screenshotControllers.forEach { (_, screenshotController) ->
if (!screenshotController.isPendingSharedTransition) {
- screenshotController.dismissScreenshot(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER)
+ screenshotController.requestDismissal(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 0991c9a..9cf347b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -53,9 +53,9 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.util.ScreenshotRequest;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.res.R;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -89,7 +89,7 @@
// TODO(b/295143676): move receiver inside executor when the flag is enabled.
mTakeScreenshotExecutor.get().onCloseSystemDialogsReceived();
} else if (!mScreenshot.isPendingSharedTransition()) {
- mScreenshot.dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
+ mScreenshot.requestDismissal(SCREENSHOT_DISMISSED_OTHER);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java b/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java
index 71c2cb4..5df6c45 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java
@@ -40,7 +40,7 @@
private final Context mContext;
private Runnable mOnTimeout;
- private int mDefaultTimeout = DEFAULT_TIMEOUT_MILLIS;
+ int mDefaultTimeout = DEFAULT_TIMEOUT_MILLIS;
@Inject
public TimeoutHandler(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index a00c81d..cdb9abb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -86,7 +86,8 @@
ScreenshotSoundControllerImpl screenshotSoundProviderImpl);
@Provides
- static ScreenshotViewProxy.Factory providesScreenshotViewProxyFactory() {
- return new LegacyScreenshotViewProxy.Factory();
+ static ScreenshotViewProxy.Factory providesScreenshotViewProxyFactory(
+ LegacyScreenshotViewProxy.Factory legacyScreenshotViewProxyFactory) {
+ return legacyScreenshotViewProxyFactory;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index e92630f..92d6ec97 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -63,7 +63,7 @@
public class BrightnessController implements ToggleSlider.Listener, MirroredBrightnessController {
private static final String TAG = "CentralSurfaces.BrightnessController";
- private static final int SLIDER_ANIMATION_DURATION = 1000;
+ private static final int SLIDER_ANIMATION_DURATION = 3000;
private static final int MSG_UPDATE_SLIDER = 1;
private static final int MSG_ATTACH_LISTENER = 2;
@@ -96,6 +96,7 @@
};
private volatile boolean mAutomatic; // Brightness adjusted automatically using ambient light.
+ private boolean mTrackingTouch = false; // Brightness adjusted via touch events.
private volatile boolean mIsVrModeEnabled;
private boolean mListening;
private boolean mExternalChange;
@@ -330,6 +331,7 @@
@Override
public void onChanged(boolean tracking, int value, boolean stopTracking) {
+ mTrackingTouch = tracking;
if (mExternalChange) return;
if (mSliderAnimator != null) {
@@ -396,6 +398,12 @@
}
}
+ private boolean triggeredByBrightnessKey() {
+ // When the brightness mode is manual and the user isn't changing the brightness via the
+ // brightness slider, assume changes are coming from a brightness key.
+ return !mAutomatic && !mTrackingTouch;
+ }
+
private void updateSlider(float brightnessValue, boolean inVrMode) {
final float min = mBrightnessMin;
final float max = mBrightnessMax;
@@ -417,12 +425,13 @@
}
private void animateSliderTo(int target) {
- if (!mControlValueInitialized || !mControl.isVisible()) {
+ if (!mControlValueInitialized || !mControl.isVisible() || triggeredByBrightnessKey()) {
// Don't animate the first value since its default state isn't meaningful to users.
// We also don't want to animate slider if it's not visible - especially important when
// two sliders are active at the same time in split shade (one in QS and one in QQS),
// as this negatively affects transition between them and they share mirror slider -
- // animating it from two different sources causes janky motion
+ // animating it from two different sources causes janky motion.
+ // Don't animate if the value is changed via the brightness keys of a keyboard.
mControl.setValue(target);
mControlValueInitialized = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index 861a2ed..539b0c2 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -19,6 +19,7 @@
import static com.android.systemui.Flags.hapticBrightnessSlider;
import android.content.Context;
+import android.content.Intent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -33,6 +34,7 @@
import com.android.systemui.classifier.Classifier;
import com.android.systemui.haptics.slider.HapticSliderViewBinder;
import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.VibratorHelper;
@@ -62,6 +64,7 @@
private final UiEventLogger mUiEventLogger;
private final SeekableSliderHapticPlugin mBrightnessSliderHapticPlugin;
+ private final ActivityStarter mActivityStarter;
private final Gefingerpoken mOnInterceptListener = new Gefingerpoken() {
@Override
@@ -84,11 +87,13 @@
BrightnessSliderView brightnessSliderView,
FalsingManager falsingManager,
UiEventLogger uiEventLogger,
- SeekableSliderHapticPlugin brightnessSliderHapticPlugin) {
+ SeekableSliderHapticPlugin brightnessSliderHapticPlugin,
+ ActivityStarter activityStarter) {
super(brightnessSliderView);
mFalsingManager = falsingManager;
mUiEventLogger = uiEventLogger;
mBrightnessSliderHapticPlugin = brightnessSliderHapticPlugin;
+ mActivityStarter = activityStarter;
}
/**
@@ -131,7 +136,15 @@
@Override
public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
- mView.setEnforcedAdmin(admin);
+ if (admin == null) {
+ mView.setAdminBlocker(null);
+ } else {
+ mView.setAdminBlocker(() -> {
+ Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(admin);
+ mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
+ return true;
+ });
+ }
}
private void setMirror(ToggleSlider toggleSlider) {
@@ -259,18 +272,21 @@
private final UiEventLogger mUiEventLogger;
private final VibratorHelper mVibratorHelper;
private final SystemClock mSystemClock;
+ private final ActivityStarter mActivityStarter;
@Inject
public Factory(
FalsingManager falsingManager,
UiEventLogger uiEventLogger,
VibratorHelper vibratorHelper,
- SystemClock clock
+ SystemClock clock,
+ ActivityStarter activityStarter
) {
mFalsingManager = falsingManager;
mUiEventLogger = uiEventLogger;
mVibratorHelper = vibratorHelper;
mSystemClock = clock;
+ mActivityStarter = activityStarter;
}
/**
@@ -292,7 +308,8 @@
if (hapticBrightnessSlider()) {
HapticSliderViewBinder.bind(viewRoot, plugin);
}
- return new BrightnessSliderController(root, mFalsingManager, mUiEventLogger, plugin);
+ return new BrightnessSliderController(
+ root, mFalsingManager, mUiEventLogger, plugin, mActivityStarter);
}
/** Get the layout to inflate based on what slider to use */
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
index c43d20c..92006a4 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
@@ -31,7 +31,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.settingslib.RestrictedLockUtils;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.res.R;
@@ -120,9 +119,8 @@
* @param admin
* @see ToggleSeekBar#setEnforcedAdmin
*/
- public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
- mSlider.setEnabled(admin == null);
- mSlider.setEnforcedAdmin(admin);
+ void setAdminBlocker(ToggleSeekBar.AdminBlocker blocker) {
+ mSlider.setAdminBlocker(blocker);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
index a5a0ae7..288ff09 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
@@ -17,20 +17,15 @@
package com.android.systemui.settings.brightness;
import android.content.Context;
-import android.content.Intent;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.SeekBar;
-import com.android.settingslib.RestrictedLockUtils;
-import com.android.systemui.Dependency;
-import com.android.systemui.plugins.ActivityStarter;
-
public class ToggleSeekBar extends SeekBar {
private String mAccessibilityLabel;
- private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin = null;
+ private AdminBlocker mAdminBlocker;
public ToggleSeekBar(Context context) {
super(context);
@@ -46,10 +41,7 @@
@Override
public boolean onTouchEvent(MotionEvent event) {
- if (mEnforcedAdmin != null) {
- Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
- mContext, mEnforcedAdmin);
- Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(intent, 0);
+ if (mAdminBlocker != null && mAdminBlocker.block()) {
return true;
}
if (!isEnabled()) {
@@ -71,7 +63,12 @@
}
}
- public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
- mEnforcedAdmin = admin;
+ void setAdminBlocker(AdminBlocker blocker) {
+ mAdminBlocker = blocker;
+ setEnabled(blocker == null);
+ }
+
+ interface AdminBlocker {
+ boolean block();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index d3869ba..3169e9c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -33,6 +33,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import com.android.systemui.util.kotlin.collectFlow
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -47,6 +48,7 @@
constructor(
private val communalInteractor: CommunalInteractor,
private val communalViewModel: CommunalViewModel,
+ private val dialogFactory: SystemUIDialogFactory,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val shadeInteractor: ShadeInteractor,
private val powerManager: PowerManager,
@@ -119,7 +121,14 @@
): View {
return initView(
ComposeView(context).apply {
- setContent { PlatformTheme { CommunalContainer(viewModel = communalViewModel) } }
+ setContent {
+ PlatformTheme {
+ CommunalContainer(
+ viewModel = communalViewModel,
+ dialogFactory = dialogFactory,
+ )
+ }
+ }
}
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 2fd438b..8b791de 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -246,6 +246,7 @@
import javax.inject.Provider;
import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.flow.Flow;
@SysUISingleton
public final class NotificationPanelViewController implements ShadeSurface, Dumpable {
@@ -1197,7 +1198,8 @@
/* excludeNotifications=*/ true), mMainDispatcher);
collectFlow(mView, mPrimaryBouncerToGoneTransitionViewModel.getNotificationAlpha(),
(Float alpha) -> {
- mNotificationStackScrollLayoutController.setMaxAlphaForExpansion(alpha);
+ mNotificationStackScrollLayoutController.setMaxAlphaForKeyguard(alpha,
+ "mPrimaryBouncerToGoneTransitionViewModel.getNotificationAlpha()");
}, mMainDispatcher);
}
}
@@ -1288,10 +1290,9 @@
mView.getContext().getDisplay());
mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
mKeyguardStatusViewController.init();
- }
- mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
- mKeyguardStatusViewController.getView().addOnLayoutChangeListener(
+ mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
+ mKeyguardStatusViewController.getView().addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
int oldHeight = oldBottom - oldTop;
if (v.getHeight() != oldHeight) {
@@ -1299,7 +1300,8 @@
}
});
- updateClockAppearance();
+ updateClockAppearance();
+ }
}
@Override
@@ -1326,7 +1328,9 @@
private void onSplitShadeEnabledChanged() {
mShadeLog.logSplitShadeChanged(mSplitShadeEnabled);
- mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
+ if (!migrateClocksToBlueprint()) {
+ mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
+ }
// Reset any left over overscroll state. It is a rare corner case but can happen.
mQsController.setOverScrollAmount(0);
mScrimController.setNotificationsOverScrollAmount(0);
@@ -1441,11 +1445,13 @@
mStatusBarStateListener.onDozeAmountChanged(mStatusBarStateController.getDozeAmount(),
mStatusBarStateController.getInterpolatedDozeAmount());
- mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
- mBarState,
- false,
- false,
- mBarState);
+ if (!migrateClocksToBlueprint()) {
+ mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
+ mBarState,
+ false,
+ false,
+ mBarState);
+ }
if (mKeyguardQsUserSwitchController != null) {
mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility(
mBarState,
@@ -1534,13 +1540,13 @@
mKeyguardBottomArea = keyguardBottomArea;
}
- @Override
+ /** Sets a listener to be notified when the shade starts opening or finishes closing. */
public void setOpenCloseListener(OpenCloseListener openCloseListener) {
SceneContainerFlag.assertInLegacyMode();
mOpenCloseListener = openCloseListener;
}
- @Override
+ /** Sets a listener to be notified when touch tracking begins. */
public void setTrackingStartedListener(TrackingStartedListener trackingStartedListener) {
mTrackingStartedListener = trackingStartedListener;
}
@@ -1665,13 +1671,11 @@
mKeyguardStatusViewController.setLockscreenClockY(
mClockPositionAlgorithm.getExpandedPreferredClockY());
}
- if (keyguardBottomAreaRefactor()) {
- mKeyguardInteractor.setClockPosition(
- mClockPositionResult.clockX, mClockPositionResult.clockY);
- } else {
+ if (!(migrateClocksToBlueprint() || keyguardBottomAreaRefactor())) {
mKeyguardBottomAreaInteractor.setClockPosition(
mClockPositionResult.clockX, mClockPositionResult.clockY);
}
+
boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
@@ -1750,12 +1754,11 @@
private void updateKeyguardStatusViewAlignment(boolean animate) {
boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
- ConstraintLayout layout;
if (migrateClocksToBlueprint()) {
- layout = mKeyguardViewConfigurator.getKeyguardRootView();
- } else {
- layout = mNotificationContainerParent;
+ mKeyguardInteractor.setClockShouldBeCentered(shouldBeCentered);
+ return;
}
+ ConstraintLayout layout = mNotificationContainerParent;
mKeyguardStatusViewController.updateAlignment(
layout, mSplitShadeEnabled, shouldBeCentered, animate);
mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(shouldBeCentered));
@@ -1979,7 +1982,6 @@
mNotificationStackScrollLayoutController.resetScrollPosition();
}
- @Override
public void collapse(boolean animate, boolean delayed, float speedUpFactor) {
boolean waiting = false;
if (animate && !isFullyCollapsed()) {
@@ -1997,7 +1999,6 @@
}
}
- @Override
public void collapse(boolean delayed, float speedUpFactor) {
if (!canBeCollapsed()) {
return;
@@ -2603,7 +2604,6 @@
return maxHeight;
}
- @Override
public boolean isExpandingOrCollapsing() {
float lockscreenExpansionProgress = mQsController.getLockscreenShadeDragProgress();
return mIsExpandingOrCollapsing
@@ -2719,7 +2719,8 @@
&& !mQsController.getFullyExpanded()) {
alpha *= mClockPositionResult.clockAlpha;
}
- mNotificationStackScrollLayoutController.setMaxAlphaForExpansion(alpha);
+ mNotificationStackScrollLayoutController.setMaxAlphaForKeyguard(alpha,
+ "NPVC.updateNotificationTranslucency()");
}
}
@@ -2770,7 +2771,9 @@
}
private void onExpandingFinished() {
- mNotificationStackScrollLayoutController.onExpansionStopped();
+ if (!SceneContainerFlag.isEnabled()) {
+ mNotificationStackScrollLayoutController.onExpansionStopped();
+ }
mHeadsUpManager.onExpandingFinished();
mConversationNotificationManager.onNotificationPanelExpandStateChanged(isFullyCollapsed());
mIsExpandingOrCollapsing = false;
@@ -3085,7 +3088,9 @@
// The expandedHeight is always the full panel Height when bypassing
expandedHeight = getMaxPanelHeight();
}
- mNotificationStackScrollLayoutController.setExpandedHeight(expandedHeight);
+ if (!SceneContainerFlag.isEnabled()) {
+ mNotificationStackScrollLayoutController.setExpandedHeight(expandedHeight);
+ }
updateKeyguardBottomAreaAlpha();
updateStatusBarIcons();
}
@@ -3316,6 +3321,9 @@
/** Updates the views to the initial state for the fold to AOD animation. */
@Override
public void prepareFoldToAodAnimation() {
+ if (migrateClocksToBlueprint()) {
+ return;
+ }
// Force show AOD UI even if we are not locked
showAodUi();
@@ -3337,6 +3345,9 @@
@Override
public void startFoldToAodAnimation(Runnable startAction, Runnable endAction,
Runnable cancelAction) {
+ if (migrateClocksToBlueprint()) {
+ return;
+ }
final ViewPropertyAnimator viewAnimator = mView.animate();
viewAnimator.cancel();
viewAnimator
@@ -3372,6 +3383,9 @@
/** Cancels fold to AOD transition and resets view state. */
@Override
public void cancelFoldToAodAnimation() {
+ if (migrateClocksToBlueprint()) {
+ return;
+ }
cancelAnimation();
resetAlpha();
resetTranslation();
@@ -3991,11 +4005,15 @@
}
@Override
+ public Flow<Float> getLegacyPanelExpansion() {
+ return mShadeRepository.getLegacyShadeExpansion();
+ }
+
+ @Override
public boolean isFullyExpanded() {
return mExpandedHeight >= getMaxPanelTransitionDistance();
}
- @Override
public boolean isShadeFullyExpanded() {
if (mBarState == SHADE) {
return isFullyExpanded();
@@ -4026,7 +4044,6 @@
return !isFullyCollapsed() && !isTracking() && !isClosing();
}
- @Override
public void instantCollapse() {
abortAnimations();
setExpandedFraction(0f);
@@ -4446,11 +4463,13 @@
}
}
- mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
- statusBarState,
- keyguardFadingAway,
- goingToFullShade,
- mBarState);
+ if (!migrateClocksToBlueprint()) {
+ mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
+ statusBarState,
+ keyguardFadingAway,
+ goingToFullShade,
+ mBarState);
+ }
if (!keyguardBottomAreaRefactor()) {
setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
@@ -4718,7 +4737,7 @@
return (Float alpha) -> {
mKeyguardStatusViewController.setAlpha(alpha);
if (!excludeNotifications) {
- stackScroller.setMaxAlphaForExpansion(alpha);
+ stackScroller.setMaxAlphaForKeyguard(alpha, "NPVC.setTransitionAlpha()");
}
if (keyguardBottomAreaRefactor()) {
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt b/packages/SystemUI/src/com/android/systemui/shade/OpenCloseListener.kt
similarity index 66%
rename from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt
rename to packages/SystemUI/src/com/android/systemui/shade/OpenCloseListener.kt
index 98a9e93..108dd478 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/OpenCloseListener.kt
@@ -14,16 +14,13 @@
* limitations under the License.
*/
-package com.android.credentialmanager.ui.screens
+package com.android.systemui.shade
-import androidx.activity.result.IntentSenderRequest
+/** Listens for when shade begins opening or finishes closing. */
+interface OpenCloseListener {
+ /** Called when the shade finishes closing. */
+ fun onClosingFinished()
-sealed class UiState {
- data object CredentialScreen : UiState()
-
- data class CredentialSelected(
- val intentSenderRequest: IntentSenderRequest?
- ) : UiState()
-
- data object Cancel : UiState()
+ /** Called when the shade starts opening. */
+ fun onOpenStarted()
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index 19d98a0..fd68c56 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -76,6 +76,7 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.shade.data.repository.ShadeRepository;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
@@ -964,7 +965,9 @@
// Reset scroll position and apply that position to the expanded height.
float height = mExpansionHeight;
setExpansionHeight(height);
- mNotificationStackScrollLayoutController.checkSnoozeLeavebehind();
+ if (!SceneContainerFlag.isEnabled()) {
+ mNotificationStackScrollLayoutController.checkSnoozeLeavebehind();
+ }
// When expanding QS, let's authenticate the user if possible,
// this will speed up notification actions.
@@ -1109,7 +1112,9 @@
/** Called when shade starts expanding. */
void onExpandingStarted(boolean qsFullyExpanded) {
- mNotificationStackScrollLayoutController.onExpansionStarted();
+ if (!SceneContainerFlag.isEnabled()) {
+ mNotificationStackScrollLayoutController.onExpansionStarted();
+ }
mExpandedWhenExpandingStarted = qsFullyExpanded;
mMediaHierarchyManager.setCollapsingShadeFromQS(mExpandedWhenExpandingStarted
/* We also start expanding when flinging closed Qs. Let's exclude that */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index 27168a7..177c3db 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -89,8 +89,7 @@
override fun isShadeFullyOpen(): Boolean = shadeInteractor.isAnyFullyExpanded.value
- override fun isExpandingOrCollapsing(): Boolean =
- shadeInteractor.anyExpansion.value > 0f && shadeInteractor.anyExpansion.value < 1f
+ override fun isExpandingOrCollapsing(): Boolean = shadeInteractor.isUserInteracting.value
override fun instantExpandShade() {
// Do nothing
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
index 4054a86..25e27ae 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
@@ -21,6 +21,7 @@
import com.android.systemui.shade.data.repository.PrivacyChipRepositoryImpl
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.shade.data.repository.ShadeRepositoryImpl
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorEmptyImpl
import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
@@ -66,5 +67,11 @@
@Binds
@SysUISingleton
+ abstract fun bindsPanelExpansionInteractor(
+ sbai: ShadeViewControllerEmptyImpl
+ ): PanelExpansionInteractor
+
+ @Binds
+ @SysUISingleton
abstract fun bindsPrivacyChipRepository(impl: PrivacyChipRepositoryImpl): PrivacyChipRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLockscreenInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLockscreenInteractor.kt
index a9ba6f9..859fce5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLockscreenInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLockscreenInteractor.kt
@@ -27,9 +27,6 @@
*/
@Deprecated("Use ShadeInteractor instead") fun expandToNotifications()
- /** Returns whether the shade is expanding or collapsing itself or quick settings. */
- val isExpandingOrCollapsing: Boolean
-
/**
* Returns whether the shade height is greater than zero (i.e. partially or fully expanded),
* there is a HUN, the shade is animating, or the shade is instantly expanding.
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 504dbfd..3e9a32b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -25,6 +25,8 @@
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.shade.data.repository.ShadeRepositoryImpl
import com.android.systemui.shade.domain.interactor.BaseShadeInteractor
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractorImpl
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorLegacyImpl
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractorSceneContainerImpl
@@ -116,6 +118,20 @@
@Provides
@SysUISingleton
+ fun providePanelExpansionInteractor(
+ sceneContainerFlags: SceneContainerFlags,
+ sceneContainerOn: Provider<PanelExpansionInteractorImpl>,
+ sceneContainerOff: Provider<NotificationPanelViewController>
+ ): PanelExpansionInteractor {
+ return if (sceneContainerFlags.isEnabled()) {
+ sceneContainerOn.get()
+ } else {
+ sceneContainerOff.get()
+ }
+ }
+
+ @Provides
+ @SysUISingleton
fun provideQuickSettingsController(
sceneContainerFlags: SceneContainerFlags,
sceneContainerOn: Provider<QuickSettingsControllerSceneImpl>,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
index 941c6f3..5c276b1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shade
import android.view.ViewPropertyAnimator
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
import com.android.systemui.statusbar.GestureRecorder
import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -26,7 +27,11 @@
* this class. If any method in this class is needed outside of CentralSurfacesImpl, it must be
* pulled up into ShadeViewController.
*/
-interface ShadeSurface : ShadeViewController, ShadeBackActionInteractor, ShadeLockscreenInteractor {
+interface ShadeSurface :
+ ShadeViewController,
+ ShadeBackActionInteractor,
+ ShadeLockscreenInteractor,
+ PanelExpansionInteractor {
/** Initialize objects instead of injecting to avoid circular dependencies. */
fun initDependencies(
centralSurfaces: CentralSurfaces,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index 7a1637e..a343ded 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -31,37 +31,12 @@
* @see NotificationPanelViewController
*/
interface ShadeViewController {
- /** Returns whether the shade is expanding or collapsing itself or quick settings. */
- val isExpandingOrCollapsing: Boolean
-
/**
* Returns whether the shade height is greater than zero or the shade is expecting a synthesized
* down event.
*/
val isPanelExpanded: Boolean
- /** Returns whether the shade is fully expanded in either QS or QQS. */
- val isShadeFullyExpanded: Boolean
-
- /**
- * Animates the collapse of a shade with the given delay and the default duration divided by
- * speedUpFactor.
- */
- fun collapse(delayed: Boolean, speedUpFactor: Float)
-
- /** Collapses the shade. */
- fun collapse(animate: Boolean, delayed: Boolean, speedUpFactor: Float)
-
- /** Collapses the shade instantly without animation. */
- fun instantCollapse()
-
- /** Returns whether the shade can be collapsed. */
- @Deprecated("Do not use outside of the shade package. Not supported by scenes.")
- fun canBeCollapsed(): Boolean
-
- /** Returns whether the shade is in the process of collapsing. */
- val isCollapsing: Boolean
-
/** Returns whether shade's height is zero. */
val isFullyCollapsed: Boolean
@@ -71,15 +46,9 @@
/** Returns whether the shade's top level view is enabled. */
val isViewEnabled: Boolean
- /** Sets a listener to be notified when the shade starts opening or finishes closing. */
- fun setOpenCloseListener(openCloseListener: OpenCloseListener)
-
/** Returns whether status bar icons should be hidden when the shade is expanded. */
fun shouldHideStatusBarIconsWhenExpanded(): Boolean
- /** Sets a listener to be notified when touch tracking begins. */
- fun setTrackingStartedListener(trackingStartedListener: TrackingStartedListener)
-
/**
* Disables the shade header.
*
@@ -130,19 +99,6 @@
fun showAodUi()
/**
- * This method should not be used anymore, you should probably use [.isShadeFullyOpen] instead.
- * It was overused as indicating if shade is open or we're on keyguard/AOD. Moving forward we
- * should be explicit about the what state we're checking.
- *
- * @return if panel is covering the screen, which means we're in expanded shade or keyguard/AOD
- */
- @Deprecated(
- "depends on the state you check, use {@link #isShadeFullyExpanded()},\n" +
- "{@link #isOnAod()}, {@link #isOnKeyguard()} instead."
- )
- fun isFullyExpanded(): Boolean
-
- /**
* Sends an external (e.g. Status Bar) touch event to the Shade touch handler.
*
* This is different from [startInputFocusTransfer] as it doesn't rely on setting the launcher
@@ -269,17 +225,3 @@
/** Return the fraction of the shade that's expanded, when in lockscreen. */
val lockscreenShadeDragProgress: Float
}
-
-/** Listens for when touch tracking begins. */
-interface TrackingStartedListener {
- fun onTrackingStarted()
-}
-
-/** Listens for when shade begins opening or finishes closing. */
-interface OpenCloseListener {
- /** Called when the shade finishes closing. */
- fun onClosingFinished()
-
- /** Called when the shade starts opening. */
- fun onOpenStarted()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index 3be3f6b..c9140b5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -19,47 +19,47 @@
import android.view.MotionEvent
import android.view.ViewGroup
import android.view.ViewTreeObserver
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController
import java.util.function.Consumer
import javax.inject.Inject
+import kotlinx.coroutines.flow.flowOf
/** Empty implementation of ShadeViewController for variants with no shade. */
class ShadeViewControllerEmptyImpl @Inject constructor() :
- ShadeViewController, ShadeBackActionInteractor, ShadeLockscreenInteractor {
- override fun expandToNotifications() {}
- override val isExpandingOrCollapsing: Boolean = false
- override val isExpanded: Boolean = false
+ ShadeViewController,
+ ShadeBackActionInteractor,
+ ShadeLockscreenInteractor,
+ PanelExpansionInteractor {
+ @Deprecated("Use ShadeInteractor instead") override fun expandToNotifications() {}
+ @Deprecated("Use ShadeInteractor instead") override val isExpanded: Boolean = false
override val isPanelExpanded: Boolean = false
- override val isShadeFullyExpanded: Boolean = false
- override fun collapse(delayed: Boolean, speedUpFactor: Float) {}
- override fun collapse(animate: Boolean, delayed: Boolean, speedUpFactor: Float) {}
- override fun instantCollapse() {}
override fun animateCollapseQs(fullyCollapse: Boolean) {}
override fun canBeCollapsed(): Boolean = false
- override val isCollapsing: Boolean = false
+ @Deprecated("Use ShadeAnimationInteractor instead") override val isCollapsing: Boolean = false
override val isFullyCollapsed: Boolean = false
override val isTracking: Boolean = false
override val isViewEnabled: Boolean = false
- override fun setOpenCloseListener(openCloseListener: OpenCloseListener) {}
override fun shouldHideStatusBarIconsWhenExpanded() = false
- override fun blockExpansionForCurrentTouch() {}
- override fun setTrackingStartedListener(trackingStartedListener: TrackingStartedListener) {}
+ @Deprecated("Not supported by scenes") override fun blockExpansionForCurrentTouch() {}
override fun disableHeader(state1: Int, state2: Int, animated: Boolean) {}
override fun startExpandLatencyTracking() {}
override fun startBouncerPreHideAnimation() {}
override fun dozeTimeTick() {}
override fun resetViews(animate: Boolean) {}
override val barState: Int = 0
+ @Deprecated("Only supported by very old devices that will not adopt scenes.")
override fun closeUserSwitcherIfOpen(): Boolean {
return false
}
override fun onBackPressed() {}
+ @Deprecated("According to b/318376223, shade predictive back is not be supported.")
override fun onBackProgressed(progressFraction: Float) {}
override fun setAlpha(alpha: Int, animate: Boolean) {}
override fun setAlphaChangeAnimationEndAction(r: Runnable) {}
- override fun setPulsing(pulsing: Boolean) {}
+ @Deprecated("Not supported by scenes") override fun setPulsing(pulsing: Boolean) {}
override fun setQsScrimEnabled(qsScrimEnabled: Boolean) {}
override fun setAmbientIndicationTop(ambientIndicationTop: Int, ambientTextVisible: Boolean) {}
override fun updateSystemUiStateFlags() {}
@@ -68,14 +68,18 @@
override fun removeOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) {}
override fun transitionToExpandedShade(delay: Long) {}
- override fun resetViewGroupFade() {}
+ @Deprecated("Not supported by scenes") override fun resetViewGroupFade() {}
+ @Deprecated("Not supported by scenes")
override fun setKeyguardTransitionProgress(keyguardAlpha: Float, keyguardTranslationY: Int) {}
- override fun setOverStretchAmount(amount: Float) {}
+ @Deprecated("Not supported by scenes") override fun setOverStretchAmount(amount: Float) {}
+ @Deprecated("TODO(b/325072511) delete this")
override fun setKeyguardStatusBarAlpha(alpha: Float) {}
override fun showAodUi() {}
- override fun isFullyExpanded(): Boolean {
- return false
- }
+ @Deprecated(
+ "depends on the state you check, use {@link #isShadeFullyExpanded()},\n" +
+ "{@link #isOnAod()}, {@link #isOnKeyguard()} instead."
+ )
+ override val isFullyExpanded = false
override fun handleExternalTouch(event: MotionEvent): Boolean {
return false
}
@@ -86,6 +90,8 @@
override val shadeHeadsUpTracker = ShadeHeadsUpTrackerEmptyImpl()
override val shadeFoldAnimator = ShadeFoldAnimatorEmptyImpl()
+ @Deprecated("Use SceneInteractor.currentScene instead.")
+ override val legacyPanelExpansion = flowOf(0f)
}
class ShadeHeadsUpTrackerEmptyImpl : ShadeHeadsUpTracker {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt
index 15ec18c..c4de78b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/StartShadeModule.kt
@@ -18,6 +18,7 @@
import com.android.systemui.CoreStartable
import com.android.systemui.biometrics.AuthRippleController
+import com.android.systemui.shade.domain.startable.ShadeStartable
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
@@ -34,4 +35,9 @@
@IntoMap
@ClassKey(AuthRippleController::class)
abstract fun bindAuthRippleController(controller: AuthRippleController): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(ShadeStartable::class)
+ abstract fun provideShadeStartable(startable: ShadeStartable): CoreStartable
}
diff --git a/core/java/android/app/ondeviceintelligence/Content.aidl b/packages/SystemUI/src/com/android/systemui/shade/TrackingStartedListener.kt
similarity index 66%
copy from core/java/android/app/ondeviceintelligence/Content.aidl
copy to packages/SystemUI/src/com/android/systemui/shade/TrackingStartedListener.kt
index 40f0ef9..3803c27 100644
--- a/core/java/android/app/ondeviceintelligence/Content.aidl
+++ b/packages/SystemUI/src/com/android/systemui/shade/TrackingStartedListener.kt
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2024, The Android Open Source Project
+/*
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package android.app.ondeviceintelligence;
+package com.android.systemui.shade
-/**
- * @hide
- */
-parcelable Content;
+/** Listens for when touch tracking begins. */
+interface TrackingStartedListener {
+ fun onTrackingStarted()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index e5ff977..5c79e1e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shade.data.repository
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.shared.model.ShadeMode
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -100,12 +101,16 @@
@Deprecated("Use ShadeInteractor.isQsBypassingShade instead")
val legacyExpandImmediate: StateFlow<Boolean>
+ val shadeMode: StateFlow<ShadeMode>
+
/** True when QS is taking up the entire screen, i.e. fully expanded on a non-unfolded phone. */
@Deprecated("Use ShadeInteractor instead") val legacyQsFullscreen: StateFlow<Boolean>
/** NPVC.mClosing as a flow. */
@Deprecated("Use ShadeAnimationInteractor instead") val legacyIsClosing: StateFlow<Boolean>
+ fun setShadeMode(mode: ShadeMode)
+
/** Sets whether a closing animation is happening. */
@Deprecated("Use ShadeAnimationInteractor instead") fun setLegacyIsClosing(isClosing: Boolean)
@@ -214,6 +219,13 @@
@Deprecated("Use ShadeInteractor instead")
override val legacyQsFullscreen: StateFlow<Boolean> = _legacyQsFullscreen.asStateFlow()
+ val _shadeMode = MutableStateFlow<ShadeMode>(ShadeMode.Single)
+ override val shadeMode: StateFlow<ShadeMode> = _shadeMode.asStateFlow()
+
+ override fun setShadeMode(shadeMode: ShadeMode) {
+ _shadeMode.value = shadeMode
+ }
+
override fun setLegacyQsFullscreen(legacyQsFullscreen: Boolean) {
_legacyQsFullscreen.value = legacyQsFullscreen
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt
new file mode 100644
index 0000000..bd96a33
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.shade.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Expansion-related methods used throughout SysUI before the addition of the scene container as the
+ * top layer component. This interface exists to allow the scene container to fulfil
+ * NotificationPanelViewController's contracts with the rest of SysUI. Once the scene container is
+ * the only shade implementation in SysUI, the remaining implementation of this should be deleted
+ * after inlining all of its method bodies. No new calls to any of these methods should be added.
+ */
+@SysUISingleton
+@Deprecated("Use ShadeInteractor instead.")
+interface PanelExpansionInteractor {
+ /**
+ * The amount by which the "panel" has been expanded (`0` when fully collapsed, `1` when fully
+ * expanded).
+ *
+ * This is a legacy concept from the time when the "panel" included the notification/QS shades
+ * as well as the keyguard (lockscreen and bouncer). This value is meant only for
+ * backwards-compatibility and should not be consumed by newer code.
+ */
+ @Deprecated("Use SceneInteractor.currentScene instead.") val legacyPanelExpansion: Flow<Float>
+
+ /**
+ * This method should not be used anymore, you should probably use [.isShadeFullyOpen] instead.
+ * It was overused as indicating if shade is open or we're on keyguard/AOD. Moving forward we
+ * should be explicit about the what state we're checking.
+ *
+ * @return if panel is covering the screen, which means we're in expanded shade or keyguard/AOD
+ */
+ @Deprecated(
+ "depends on the state you check, use {@link #isShadeFullyExpanded()},\n" +
+ "{@link #isOnAod()}, {@link #isOnKeyguard()} instead."
+ )
+ val isFullyExpanded: Boolean
+
+ /** Returns whether the shade is in the process of collapsing. */
+ @Deprecated("Use ShadeAnimationInteractor instead") val isCollapsing: Boolean
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
new file mode 100644
index 0000000..c5a6968
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.shade.domain.interactor
+
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+@SysUISingleton
+class PanelExpansionInteractorImpl
+@Inject
+constructor(
+ sceneInteractor: SceneInteractor,
+ shadeInteractor: ShadeInteractor,
+ shadeAnimationInteractor: ShadeAnimationInteractor,
+) : PanelExpansionInteractor {
+
+ /**
+ * The amount by which the "panel" has been expanded (`0` when fully collapsed, `1` when fully
+ * expanded).
+ *
+ * This is a legacy concept from the time when the "panel" included the notification/QS shades
+ * as well as the keyguard (lockscreen and bouncer). This value is meant only for
+ * backwards-compatibility and should not be consumed by newer code.
+ */
+ @Deprecated("Use SceneInteractor.currentScene instead.")
+ override val legacyPanelExpansion: Flow<Float> =
+ sceneInteractor.transitionState.flatMapLatest { state ->
+ when (state) {
+ is ObservableTransitionState.Idle ->
+ flowOf(
+ if (state.scene != Scenes.Gone) {
+ // When resting on a non-Gone scene, the panel is fully expanded.
+ 1f
+ } else {
+ // When resting on the Gone scene, the panel is considered fully
+ // collapsed.
+ 0f
+ }
+ )
+ is ObservableTransitionState.Transition ->
+ when {
+ state.fromScene == Scenes.Gone ->
+ if (state.toScene.isExpandable()) {
+ // Moving from Gone to a scene that can animate-expand has a
+ // panel
+ // expansion
+ // that tracks with the transition.
+ state.progress
+ } else {
+ // Moving from Gone to a scene that doesn't animate-expand
+ // immediately makes
+ // the panel fully expanded.
+ flowOf(1f)
+ }
+ state.toScene == Scenes.Gone ->
+ if (state.fromScene.isExpandable()) {
+ // Moving to Gone from a scene that can animate-expand has a
+ // panel
+ // expansion
+ // that tracks with the transition.
+ state.progress.map { 1 - it }
+ } else {
+ // Moving to Gone from a scene that doesn't animate-expand
+ // immediately makes
+ // the panel fully collapsed.
+ flowOf(0f)
+ }
+ else -> flowOf(1f)
+ }
+ }
+ }
+
+ @Deprecated(
+ "depends on the state you check, use {@link #isShadeFullyExpanded()},\n" +
+ "{@link #isOnAod()}, {@link #isOnKeyguard()} instead."
+ )
+ override val isFullyExpanded = shadeInteractor.isAnyFullyExpanded.value
+
+ @Deprecated("Use ShadeAnimationInteractor instead")
+ override val isCollapsing =
+ shadeAnimationInteractor.isAnyCloseAnimationRunning.value ||
+ shadeAnimationInteractor.isLaunchingActivity.value
+ private fun SceneKey.isExpandable(): Boolean {
+ return this == Scenes.Shade || this == Scenes.QuickSettings
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt
index 5a777e8..134c983 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt
@@ -17,7 +17,6 @@
package com.android.systemui.shade.domain.interactor
import com.android.systemui.shade.data.repository.ShadeAnimationRepository
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -38,5 +37,5 @@
* completes the close. Important: if QS is collapsing back to shade, this will be false because
* that is not considered "closing".
*/
- abstract val isAnyCloseAnimationRunning: Flow<Boolean>
+ abstract val isAnyCloseAnimationRunning: StateFlow<Boolean>
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt
index 2a7658a..f364d6d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt
@@ -19,7 +19,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.shade.data.repository.ShadeAnimationRepository
import javax.inject.Inject
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.MutableStateFlow
/** Implementation of ShadeAnimationInteractor for shadeless SysUI variants. */
@SysUISingleton
@@ -28,5 +28,5 @@
constructor(
shadeAnimationRepository: ShadeAnimationRepository,
) : ShadeAnimationInteractor(shadeAnimationRepository) {
- override val isAnyCloseAnimationRunning = flowOf(false)
+ override val isAnyCloseAnimationRunning = MutableStateFlow(false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
index eaac8ae..d9982e3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt
@@ -18,21 +18,26 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.ShadeAnimationRepository
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
/** Implementation of ShadeAnimationInteractor compatible with the scene container framework. */
@SysUISingleton
class ShadeAnimationInteractorSceneContainerImpl
@Inject
constructor(
+ @Background scope: CoroutineScope,
shadeAnimationRepository: ShadeAnimationRepository,
sceneInteractor: SceneInteractor,
) : ShadeAnimationInteractor(shadeAnimationRepository) {
@@ -56,4 +61,5 @@
}
}
.distinctUntilChanged()
+ .stateIn(scope, SharingStarted.Eagerly, false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index 43ede2a..bc60c83 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shade.domain.interactor
+import com.android.systemui.shade.shared.model.ShadeMode
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
@@ -102,6 +103,8 @@
* animating.
*/
val isUserInteractingWithQs: Flow<Boolean>
+
+ val shadeMode: StateFlow<ShadeMode>
}
fun createAnyExpansionFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
index 55dd674..e9bb4c6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade.domain.interactor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.shared.model.ShadeMode
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -42,4 +43,5 @@
override val isUserInteracting: StateFlow<Boolean> = inactiveFlowBoolean
override val isShadeTouchable: Flow<Boolean> = inactiveFlowBoolean
override val isExpandToQsEnabled: Flow<Boolean> = inactiveFlowBoolean
+ override val shadeMode: StateFlow<ShadeMode> = MutableStateFlow(ShadeMode.Single)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
index 2ac3193..6414af3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
@@ -21,6 +21,7 @@
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -99,6 +100,8 @@
override val isUserInteractingWithQs: Flow<Boolean> =
userInteractingFlow(repository.legacyQsTracking, repository.qsExpansion)
+ override val shadeMode: StateFlow<ShadeMode> = repository.shadeMode
+
/**
* Return a flow for whether a user is interacting with an expandable shade component using
* tracking and expansion flows. NOTE: expansion must be a `StateFlow` to guarantee that
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
index 67cac3d..7785eda 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
@@ -22,6 +22,8 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -45,6 +47,7 @@
@Application scope: CoroutineScope,
sceneInteractor: SceneInteractor,
sharedNotificationContainerInteractor: SharedNotificationContainerInteractor,
+ shadeRepository: ShadeRepository,
) : BaseShadeInteractor {
override val shadeExpansion: Flow<Float> = sceneBasedExpansion(sceneInteractor, Scenes.Shade)
@@ -106,6 +109,8 @@
override val isUserInteractingWithQs: Flow<Boolean> =
sceneBasedInteracting(sceneInteractor, Scenes.QuickSettings)
+ override val shadeMode: StateFlow<ShadeMode> = shadeRepository.shadeMode
+
/**
* Returns a flow that uses scene transition progress to and from a scene that is pulled down
* from the top of the screen to a 0-1 expansion amount float.
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
index 1f78ae8..3d9337e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
@@ -38,8 +38,6 @@
changeToShadeScene()
}
- override val isExpandingOrCollapsing = shadeInteractor.isUserInteracting.value
-
override val isExpanded = shadeInteractor.isAnyExpanded.value
override fun startBouncerPreHideAnimation() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
new file mode 100644
index 0000000..11ce818
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.startable
+
+import android.content.Context
+import com.android.systemui.CoreStartable
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.statusbar.policy.SplitShadeStateController
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class ShadeStartable
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ @Application private val applicationContext: Context,
+ private val configurationRepository: ConfigurationRepository,
+ private val shadeRepository: ShadeRepository,
+ private val controller: SplitShadeStateController,
+) : CoreStartable {
+
+ override fun start() {
+ hydrateShadeMode()
+ }
+
+ private fun hydrateShadeMode() {
+ applicationScope.launch {
+ configurationRepository.onAnyConfigurationChange
+ // Force initial collection.
+ .onStart { emit(Unit) }
+ .map { applicationContext.resources }
+ .map { resources -> controller.shouldUseSplitNotificationShade(resources) }
+ .collect { isSplitShade ->
+ shadeRepository.setShadeMode(
+ if (isSplitShade) {
+ ShadeMode.Split
+ } else {
+ ShadeMode.Single
+ }
+ )
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt
new file mode 100644
index 0000000..3451eaf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.shared.model
+
+/** Enumerates all known modes of operation of the shade. */
+sealed interface ShadeMode {
+
+ /**
+ * The single or "accordion" shade where the QS and notification parts are in two vertically
+ * stacked panels and the user can swipe up and down to expand or collapse between the two
+ * parts.
+ */
+ data object Single : ShadeMode
+
+ /**
+ * The split shade where, on large screens and unfolded foldables, the QS and notification parts
+ * are placed side-by-side and expand/collapse as a single panel.
+ */
+ data object Split : ShadeMode
+
+ /**
+ * The dual shade where the QS and notification parts each have their own independently
+ * expandable/collapsible panel on either side of the large screen / unfolded device or sharing
+ * a space on a small screen or folded device.
+ */
+ data object Dual : ShadeMode
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
index 84afbed..3a5c5e1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
@@ -22,7 +22,7 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.qs.QS
-import com.android.systemui.scene.domain.interactor.PanelExpansionInteractor
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.PanelState
import com.android.systemui.shade.ShadeExpansionChangeEvent
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index c9aa51c..ea549f2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -14,18 +14,32 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.shade.ui.viewmodel
+import androidx.lifecycle.LifecycleOwner
import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.qs.FooterActionsController
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
+import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
@@ -43,28 +57,36 @@
val shadeHeaderViewModel: ShadeHeaderViewModel,
val notifications: NotificationsPlaceholderViewModel,
val mediaDataManager: MediaDataManager,
+ shadeInteractor: ShadeInteractor,
+ private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
+ private val footerActionsController: FooterActionsController,
) {
- /** The key of the scene we should switch to when swiping up. */
- val upDestinationSceneKey: StateFlow<SceneKey> =
+ val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
combine(
deviceEntryInteractor.isUnlocked,
deviceEntryInteractor.canSwipeToEnter,
- ) { isUnlocked, canSwipeToDismiss ->
- upDestinationSceneKey(
+ shadeInteractor.shadeMode,
+ ) { isUnlocked, canSwipeToDismiss, shadeMode ->
+ destinationScenes(
isUnlocked = isUnlocked,
canSwipeToDismiss = canSwipeToDismiss,
+ shadeMode = shadeMode,
)
}
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
initialValue =
- upDestinationSceneKey(
+ destinationScenes(
isUnlocked = deviceEntryInteractor.isUnlocked.value,
canSwipeToDismiss = deviceEntryInteractor.canSwipeToEnter.value,
+ shadeMode = shadeInteractor.shadeMode.value,
),
)
+ private val upDestinationSceneKey: Flow<SceneKey?> =
+ destinationScenes.map { it[Swipe(SwipeDirection.Up)]?.toScene }
+
/** Whether or not the shade container should be clickable. */
val isClickable: StateFlow<Boolean> =
upDestinationSceneKey
@@ -75,22 +97,42 @@
initialValue = false
)
+ val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode
+
/** Notifies that some content in the shade was clicked. */
fun onContentClicked() = deviceEntryInteractor.attemptDeviceEntry()
- private fun upDestinationSceneKey(
- isUnlocked: Boolean,
- canSwipeToDismiss: Boolean?,
- ): SceneKey {
- return when {
- canSwipeToDismiss == true -> Scenes.Lockscreen
- isUnlocked -> Scenes.Gone
- else -> Scenes.Lockscreen
- }
- }
-
fun isMediaVisible(): Boolean {
// TODO(b/296122467): handle updates to carousel visibility while scene is still visible
return mediaDataManager.hasActiveMediaOrRecommendation()
}
+
+ private val footerActionsControllerInitialized = AtomicBoolean(false)
+
+ fun getFooterActionsViewModel(lifecycleOwner: LifecycleOwner): FooterActionsViewModel {
+ if (footerActionsControllerInitialized.compareAndSet(false, true)) {
+ footerActionsController.init()
+ }
+ return footerActionsViewModelFactory.create(lifecycleOwner)
+ }
+
+ private fun destinationScenes(
+ isUnlocked: Boolean,
+ canSwipeToDismiss: Boolean?,
+ shadeMode: ShadeMode,
+ ): Map<UserAction, UserActionResult> {
+ val up =
+ when {
+ canSwipeToDismiss == true -> Scenes.Lockscreen
+ isUnlocked -> Scenes.Gone
+ else -> Scenes.Lockscreen
+ }
+
+ val down = Scenes.QuickSettings.takeIf { shadeMode is ShadeMode.Single }
+
+ return buildMap {
+ this[Swipe(SwipeDirection.Up)] = UserActionResult(up)
+ down?.let { this[Swipe(SwipeDirection.Down)] = UserActionResult(down) }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index bb81683..4275fc6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -169,7 +169,7 @@
private static final int MSG_TILE_SERVICE_REQUEST_LISTENING_STATE = 68 << MSG_SHIFT;
private static final int MSG_SHOW_REAR_DISPLAY_DIALOG = 69 << MSG_SHIFT;
private static final int MSG_MOVE_FOCUSED_TASK_TO_FULLSCREEN = 70 << MSG_SHIFT;
- private static final int MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP = 71 << MSG_SHIFT;
+ private static final int MSG_MOVE_FOCUSED_TASK_TO_STAGE_SPLIT = 71 << MSG_SHIFT;
private static final int MSG_SHOW_MEDIA_OUTPUT_SWITCHER = 72 << MSG_SHIFT;
private static final int MSG_TOGGLE_TASKBAR = 73 << MSG_SHIFT;
private static final int MSG_SETTING_CHANGED = 74 << MSG_SHIFT;
@@ -503,9 +503,9 @@
default void moveFocusedTaskToFullscreen(int displayId) {}
/**
- * @see IStatusBar#enterStageSplitFromRunningApp
+ * @see IStatusBar#moveFocusedTaskToStageSplit
*/
- default void enterStageSplitFromRunningApp(boolean leftOrTop) {}
+ default void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop) {}
/**
* @see IStatusBar#showMediaOutputSwitcher
@@ -1338,10 +1338,13 @@
}
@Override
- public void enterStageSplitFromRunningApp(boolean leftOrTop) {
+ public void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop) {
synchronized (mLock) {
- mHandler.obtainMessage(MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP,
- leftOrTop).sendToTarget();
+ SomeArgs args = SomeArgs.obtain();
+ args.argi1 = displayId;
+ args.argi2 = leftOrTop ? 1 : 0;
+ mHandler.obtainMessage(MSG_MOVE_FOCUSED_TASK_TO_STAGE_SPLIT,
+ args).sendToTarget();
}
}
@@ -1907,11 +1910,15 @@
}
break;
}
- case MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP:
+ case MSG_MOVE_FOCUSED_TASK_TO_STAGE_SPLIT: {
+ args = (SomeArgs) msg.obj;
+ int displayId = args.argi1;
+ boolean leftOrTop = args.argi2 != 0;
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).enterStageSplitFromRunningApp((Boolean) msg.obj);
+ mCallbacks.get(i).moveFocusedTaskToStageSplit(displayId, leftOrTop);
}
break;
+ }
case MSG_SHOW_MEDIA_OUTPUT_SWITCHER:
args = (SomeArgs) msg.obj;
String clientPackageName = (String) args.arg1;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 1ec86ae..c904621 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -147,8 +147,9 @@
private static final int MSG_SHOW_ACTION_TO_UNLOCK = 1;
private static final int MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON = 2;
private static final long TRANSIENT_BIOMETRIC_ERROR_TIMEOUT = 1300;
+ public static final long DEFAULT_MESSAGE_TIME = 3500;
public static final long DEFAULT_HIDE_DELAY_MS =
- 3500 + KeyguardIndicationTextView.Y_IN_DURATION;
+ DEFAULT_MESSAGE_TIME + KeyguardIndicationTextView.Y_IN_DURATION;
private final Context mContext;
private final BroadcastDispatcher mBroadcastDispatcher;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 81f644f..4b16126 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -594,6 +594,7 @@
}
}
val cancelHandler = Runnable {
+ statusBarStateController.setLeaveOpenOnKeyguardHide(false)
draggedDownEntry?.apply {
setUserLocked(false)
notifyHeightChanged(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 6155348..5171a5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -39,8 +39,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.animation.ShadeInterpolation;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.flags.RefactorFlag;
import com.android.systemui.res.R;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
@@ -95,8 +93,6 @@
private float mCornerAnimationDistance;
private float mActualWidth = -1;
private int mMaxIconsOnLockscreen;
- private final RefactorFlag mSensitiveRevealAnim =
- RefactorFlag.forView(Flags.SENSITIVE_REVEAL_ANIM);
private boolean mCanModifyColorOfNotifications;
private boolean mCanInteract;
private NotificationStackScrollLayout mHostLayout;
@@ -266,7 +262,7 @@
}
final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight();
- if (mSensitiveRevealAnim.isEnabled() && viewState.hidden) {
+ if (viewState.hidden) {
// if the shelf is hidden, position it at the end of the stack (plus the clip
// padding), such that when it appears animated, it will smoothly move in from the
// bottom, without jump cutting any notifications
@@ -398,10 +394,6 @@
// find the first view that doesn't overlap with the shelf
int notGoneIndex = 0;
int colorOfViewBeforeLast = NO_COLOR;
- boolean backgroundForceHidden = false;
- if (mHideBackground && !((ShelfState) getViewState()).hasItemsInStableShelf) {
- backgroundForceHidden = true;
- }
int colorTwoBefore = NO_COLOR;
int previousColor = NO_COLOR;
float transitionAmount = 0.0f;
@@ -429,8 +421,7 @@
expandingAnimated, isLastChild, shelfClipStart);
// TODO(b/172289889) scale mPaddingBetweenElements with expansion amount
- if ((!mSensitiveRevealAnim.isEnabled() && ((isLastChild && !child.isInShelf())
- || backgroundForceHidden)) || aboveShelf) {
+ if (aboveShelf) {
notificationClipEnd = shelfStart + getIntrinsicHeight();
} else {
notificationClipEnd = shelfStart - mPaddingBetweenElements;
@@ -440,8 +431,7 @@
// If the current row is an ExpandableNotificationRow, update its color, roundedness,
// and icon state.
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow expandableRow = (ExpandableNotificationRow) child;
+ if (child instanceof ExpandableNotificationRow expandableRow) {
numViewsInShelf += inShelfAmount;
int ownColorUntinted = expandableRow.getBackgroundColorWithoutTint();
if (viewStart >= shelfStart && mNotGoneIndex == -1) {
@@ -471,16 +461,8 @@
notGoneIndex++;
}
- if (child instanceof ActivatableNotificationView) {
- ActivatableNotificationView anv = (ActivatableNotificationView) child;
- // Because we show whole notifications on the lockscreen, the bottom notification is
- // always "just about to enter the shelf" by normal scrolling rules. This is fine
- // if the shelf is visible, but if the shelf is hidden, it causes incorrect curling.
- // notificationClipEnd handles the discrepancy between a visible and hidden shelf,
- // so we use that when on the keyguard (and while animating away) to reduce curling.
- final float keyguardSafeShelfStart = !mSensitiveRevealAnim.isEnabled()
- && mAmbientState.isOnKeyguard() ? notificationClipEnd : shelfStart;
- updateCornerRoundnessOnScroll(anv, viewStart, keyguardSafeShelfStart);
+ if (child instanceof ActivatableNotificationView anv) {
+ updateCornerRoundnessOnScroll(anv, viewStart, shelfStart);
}
}
@@ -519,11 +501,10 @@
mShelfIcons.applyIconStates();
for (int i = 0; i < getHostLayoutChildCount(); i++) {
View child = getHostLayoutChildAt(i);
- if (!(child instanceof ExpandableNotificationRow)
+ if (!(child instanceof ExpandableNotificationRow row)
|| child.getVisibility() == GONE) {
continue;
}
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
updateContinuousClipping(row);
}
boolean hideBackground = isHidden;
@@ -613,8 +594,7 @@
private void clipTransientViews() {
for (int i = 0; i < getHostLayoutTransientViewCount(); i++) {
View transientView = getHostLayoutTransientView(i);
- if (transientView instanceof ExpandableView) {
- ExpandableView transientExpandableView = (ExpandableView) transientView;
+ if (transientView instanceof ExpandableView transientExpandableView) {
updateNotificationClipHeight(transientExpandableView, getTranslationY(), -1);
}
}
@@ -871,10 +851,9 @@
}
private void setIconTransformationAmount(ExpandableView view, float transitionAmount) {
- if (!(view instanceof ExpandableNotificationRow)) {
+ if (!(view instanceof ExpandableNotificationRow row)) {
return;
}
- ExpandableNotificationRow row = (ExpandableNotificationRow) view;
StatusBarIconView icon = row.getShelfIcon();
NotificationIconContainer.IconState iconState = getIconState(icon);
if (iconState == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
index 072f56d..dcfccd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
@@ -61,7 +61,7 @@
return false;
}
- if (!Flags.notificationsBackgroundMediaIcons()) {
+ if (!Flags.notificationsBackgroundIcons()) {
inflateOrUpdateIcons(entry);
}
@@ -73,14 +73,14 @@
@Override
public void onEntryInit(@NonNull NotificationEntry entry) {
// We default to STATE_ICONS_UNINFLATED anyway, so there's no need to initialize it.
- if (!Flags.notificationsBackgroundMediaIcons()) {
+ if (!Flags.notificationsBackgroundIcons()) {
mIconsState.put(entry, STATE_ICONS_UNINFLATED);
}
}
@Override
public void onEntryAdded(@NonNull NotificationEntry entry) {
- if (Flags.notificationsBackgroundMediaIcons()) {
+ if (Flags.notificationsBackgroundIcons()) {
if (isMediaNotification(entry.getSbn())) {
inflateOrUpdateIcons(entry);
}
@@ -94,7 +94,7 @@
mIconsState.put(entry, STATE_ICONS_UNINFLATED);
}
- if (Flags.notificationsBackgroundMediaIcons()) {
+ if (Flags.notificationsBackgroundIcons()) {
if (isMediaNotification(entry.getSbn())) {
inflateOrUpdateIcons(entry);
}
@@ -120,7 +120,7 @@
break;
case STATE_ICONS_INFLATED:
try {
- mIconManager.updateIcons(entry);
+ mIconManager.updateIcons(entry, /* usingCache = */ false);
} catch (InflationException e) {
reportInflationError(entry, e);
mIconsState.put(entry, STATE_ICONS_ERROR);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index deaf1d1..dfb0f9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -367,7 +367,8 @@
/* reason = */ reason,
/* showSnooze = */ adjustment.isSnoozeEnabled(),
/* isChildInGroup = */ adjustment.isChildInGroup(),
- /* isGroupSummary = */ adjustment.isGroupSummary()
+ /* isGroupSummary = */ adjustment.isGroupSummary(),
+ /* needsRedaction = */ adjustment.getNeedsRedaction()
);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
index f03c313..e4db4c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -63,9 +63,16 @@
SensitiveContentCoordinator,
DynamicPrivacyController.Listener,
OnBeforeRenderListListener {
+ private val onSensitiveStateChanged = Runnable() {
+ invalidateList("onSensitiveStateChanged")
+ }
override fun attach(pipeline: NotifPipeline) {
dynamicPrivacyController.addListener(this)
+ if (screenshareNotificationHiding()) {
+ sensitiveNotificationProtectionController
+ .registerSensitiveStateListener(onSensitiveStateChanged)
+ }
pipeline.addOnBeforeRenderListListener(this)
pipeline.addPreRenderInvalidator(this)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
index 18460c3..7b8a062 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
@@ -61,5 +61,6 @@
val showSnooze: Boolean,
val isChildInGroup: Boolean = false,
val isGroupSummary: Boolean = false,
+ val needsRedaction: Boolean,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
index 0b9d19d..e0e5a35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
@@ -22,6 +22,7 @@
import android.os.HandlerExecutor
import android.os.UserHandle
import android.provider.Settings.Secure.SHOW_NOTIFICATION_SNOOZE
+import com.android.server.notification.Flags.screenshareNotificationHiding
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.settings.UserTracker
@@ -30,6 +31,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
import com.android.systemui.util.ListenerSet
import com.android.systemui.util.settings.SecureSettings
import javax.inject.Inject
@@ -43,6 +45,7 @@
@Main private val handler: Handler,
private val secureSettings: SecureSettings,
private val lockscreenUserManager: NotificationLockscreenUserManager,
+ private val sensitiveNotifProtectionController: SensitiveNotificationProtectionController,
private val sectionStyleProvider: SectionStyleProvider,
private val userTracker: UserTracker,
private val groupMembershipManager: GroupMembershipManager,
@@ -66,6 +69,11 @@
fun addDirtyListener(listener: Runnable) {
if (dirtyListeners.isEmpty()) {
lockscreenUserManager.addNotificationStateChangedListener(notifStateChangedListener)
+ if (screenshareNotificationHiding()) {
+ sensitiveNotifProtectionController.registerSensitiveStateListener(
+ onSensitiveStateChangedListener
+ )
+ }
updateSnoozeEnabled()
secureSettings.registerContentObserverForUser(
SHOW_NOTIFICATION_SNOOZE,
@@ -80,6 +88,11 @@
dirtyListeners.remove(listener)
if (dirtyListeners.isEmpty()) {
lockscreenUserManager.removeNotificationStateChangedListener(notifStateChangedListener)
+ if (screenshareNotificationHiding()) {
+ sensitiveNotifProtectionController.unregisterSensitiveStateListener(
+ onSensitiveStateChangedListener
+ )
+ }
secureSettings.unregisterContentObserver(settingsObserver)
}
}
@@ -89,6 +102,8 @@
dirtyListeners.forEach(Runnable::run)
}
+ private val onSensitiveStateChangedListener = Runnable { dirtyListeners.forEach(Runnable::run) }
+
private val settingsObserver = object : ContentObserver(handler) {
override fun onChange(selfChange: Boolean) {
updateSnoozeEnabled()
@@ -122,7 +137,10 @@
isConversation = entry.ranking.isConversation,
isSnoozeEnabled = isSnoozeSettingsEnabled && !entry.isCanceled,
isMinimized = isEntryMinimized(entry),
- needsRedaction = lockscreenUserManager.needsRedaction(entry),
+ needsRedaction =
+ lockscreenUserManager.needsRedaction(entry) ||
+ (screenshareNotificationHiding() &&
+ sensitiveNotifProtectionController.shouldProtectNotification(entry)),
isChildInGroup = entry.sbn.isAppOrSystemGroupChild,
isGroupSummary = entry.sbn.isAppOrSystemGroupSummary,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index c5b55c7..4bbe035 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -138,7 +138,7 @@
if (entry.rowExists()) {
mLogger.logUpdatingRow(entry, params);
- mIconManager.updateIcons(entry);
+ mIconManager.updateIcons(entry, /* usingCache = */ false);
ExpandableNotificationRow row = entry.getRow();
row.reset();
updateRow(entry, row);
@@ -254,11 +254,9 @@
params.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
params.setUseLowPriority(isLowPriority);
- // If screenshareNotificationHiding is enabled, both public and private views should be
- // inflated to avoid any latency associated with reinflating all notification views when
- // screen share starts and stops
if (screenshareNotificationHiding()
- || mNotificationLockscreenUserManager.needsRedaction(entry)) {
+ ? inflaterParams.getNeedsRedaction()
+ : mNotificationLockscreenUserManager.needsRedaction(entry)) {
params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC);
} else {
params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index a5f42bb..a900e45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -28,14 +28,24 @@
import android.widget.ImageView
import com.android.app.tracing.traceSection
import com.android.internal.statusbar.StatusBarIcon
+import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.InflationException
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
/**
* Inflates and updates icons associated with notifications
@@ -53,9 +63,18 @@
constructor(
private val notifCollection: CommonNotifCollection,
private val launcherApps: LauncherApps,
- private val iconBuilder: IconBuilder
+ private val iconBuilder: IconBuilder,
+ @Application private val applicationCoroutineScope: CoroutineScope,
+ @Background private val bgCoroutineContext: CoroutineContext,
+ @Main private val mainCoroutineContext: CoroutineContext,
) : ConversationIconManager {
private var unimportantConversationKeys: Set<String> = emptySet()
+ /**
+ * A map of running jobs for fetching the person avatar from launcher. The key is the
+ * notification entry key.
+ */
+ private var launcherPeopleAvatarIconJobs: ConcurrentHashMap<String, Job> =
+ ConcurrentHashMap<String, Job>()
fun attach() {
notifCollection.addCollectionListener(entryListener)
@@ -136,13 +155,23 @@
* @throws InflationException Exception if required icons are not valid or specified
*/
@Throws(InflationException::class)
- fun updateIcons(entry: NotificationEntry) =
+ fun updateIcons(entry: NotificationEntry, usingCache: Boolean = false) =
traceSection("IconManager.updateIcons") {
if (!entry.icons.areIconsAvailable) {
return@traceSection
}
- entry.icons.smallIconDescriptor = null
- entry.icons.peopleAvatarDescriptor = null
+
+ if (usingCache && !Flags.notificationsBackgroundIcons()) {
+ Log.wtf(
+ TAG,
+ "Updating using the cache is not supported when the " +
+ "notifications_background_conversation_icons flag is off"
+ )
+ }
+ if (!usingCache || !Flags.notificationsBackgroundIcons()) {
+ entry.icons.smallIconDescriptor = null
+ entry.icons.peopleAvatarDescriptor = null
+ }
val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry)
val notificationContentDescription =
@@ -188,7 +217,7 @@
@Throws(InflationException::class)
private fun getIconDescriptor(entry: NotificationEntry, redact: Boolean): StatusBarIcon {
val n = entry.sbn.notification
- val showPeopleAvatar = isImportantConversation(entry) && !redact
+ val showPeopleAvatar = !redact && isImportantConversation(entry)
val peopleAvatarDescriptor = entry.icons.peopleAvatarDescriptor
val smallIconDescriptor = entry.icons.smallIconDescriptor
@@ -208,26 +237,18 @@
})
?: throw InflationException("No icon in notification from " + entry.sbn.packageName)
- val ic =
- StatusBarIcon(
- entry.sbn.user,
- entry.sbn.packageName,
- icon,
- n.iconLevel,
- n.number,
- iconBuilder.getIconContentDescription(n)
- )
+ val sbi = icon.toStatusBarIcon(entry)
// Cache if important conversation.
if (isImportantConversation(entry)) {
if (showPeopleAvatar) {
- entry.icons.peopleAvatarDescriptor = ic
+ entry.icons.peopleAvatarDescriptor = sbi
} else {
- entry.icons.smallIconDescriptor = ic
+ entry.icons.smallIconDescriptor = sbi
}
}
- return ic
+ return sbi
}
@Throws(InflationException::class)
@@ -243,16 +264,69 @@
}
}
- @Throws(InflationException::class)
- private fun createPeopleAvatar(entry: NotificationEntry): Icon? {
- var ic: Icon? = null
+ private fun Icon.toStatusBarIcon(entry: NotificationEntry): StatusBarIcon {
+ val n = entry.sbn.notification
+ return StatusBarIcon(
+ entry.sbn.user,
+ entry.sbn.packageName,
+ /* icon = */ this,
+ n.iconLevel,
+ n.number,
+ iconBuilder.getIconContentDescription(n)
+ )
+ }
- val shortcut = entry.ranking.conversationShortcutInfo
- if (shortcut != null) {
- ic = launcherApps.getShortcutIcon(shortcut)
+ private suspend fun getLauncherShortcutIconForPeopleAvatar(entry: NotificationEntry) =
+ withContext(bgCoroutineContext) {
+ var icon: Icon? = null
+ val shortcut = entry.ranking.conversationShortcutInfo
+ if (shortcut != null) {
+ try {
+ icon = launcherApps.getShortcutIcon(shortcut)
+ } catch (e: Exception) {
+ Log.e(
+ TAG,
+ "Error calling LauncherApps#getShortcutIcon for notification $entry: $e"
+ )
+ }
+ }
+
+ // Once we have the icon, updating it should happen on the main thread.
+ if (icon != null) {
+ withContext(mainCoroutineContext) {
+ val iconDescriptor = icon.toStatusBarIcon(entry)
+
+ // Cache the value
+ entry.icons.peopleAvatarDescriptor = iconDescriptor
+
+ // Update the icons using the cached value
+ updateIcons(entry = entry, usingCache = true)
+ }
+ }
}
- // Fall back to extract from message
+ @Throws(InflationException::class)
+ private fun createPeopleAvatar(entry: NotificationEntry): Icon {
+ var ic: Icon? = null
+
+ if (Flags.notificationsBackgroundIcons()) {
+ // Ideally we want to get the icon from launcher, but this is a binder transaction that
+ // may take longer so let's kick it off on a background thread and use a placeholder in
+ // the meantime.
+ // Cancel the previous job if necessary.
+ launcherPeopleAvatarIconJobs[entry.key]?.cancel()
+ launcherPeopleAvatarIconJobs[entry.key] =
+ applicationCoroutineScope
+ .launch { getLauncherShortcutIconForPeopleAvatar(entry) }
+ .apply { invokeOnCompletion { launcherPeopleAvatarIconJobs.remove(entry.key) } }
+ } else {
+ val shortcut = entry.ranking.conversationShortcutInfo
+ if (shortcut != null) {
+ ic = launcherApps.getShortcutIcon(shortcut)
+ }
+ }
+
+ // Try to extract from message
if (ic == null) {
val extras: Bundle = entry.sbn.notification.extras
val messages =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 5f3a83a..c05c3c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -278,8 +278,6 @@
private OnExpandClickListener mOnExpandClickListener;
private View.OnClickListener mOnFeedbackClickListener;
private Path mExpandingClipPath;
- private final RefactorFlag mInlineReplyAnimation =
- RefactorFlag.forView(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION);
private static boolean shouldSimulateSlowMeasure() {
return Compile.IS_DEBUG && RefactorFlag.forView(
@@ -355,7 +353,7 @@
nowExpanded = !isExpanded();
setUserExpanded(nowExpanded);
}
- notifyHeightChanged(true);
+ notifyHeightChanged(/* needsAnimation= */ true);
mOnExpandClickListener.onExpandClicked(mEntry, v, nowExpanded);
mMetricsLogger.action(MetricsEvent.ACTION_NOTIFICATION_EXPANDER, nowExpanded);
}
@@ -778,7 +776,7 @@
mChildrenContainer.updateGroupOverflow();
}
if (intrinsicBefore != getIntrinsicHeight()) {
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
}
if (isHeadsUp) {
mMustStayOnScreen = true;
@@ -826,7 +824,7 @@
if (mChildrenContainer != null) {
mChildrenContainer.setHeaderVisibleAmount(headerVisibleAmount);
}
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
}
}
@@ -1088,7 +1086,7 @@
boolean wasAboveShelf = isAboveShelf();
mIsPinned = pinned;
if (intrinsicHeight != getIntrinsicHeight()) {
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
}
if (pinned) {
setAnimationRunning(true);
@@ -2611,7 +2609,7 @@
onExpansionChanged(true /* userAction */, wasExpanded);
if (!wasExpanded && isExpanded()
&& getActualHeight() != getIntrinsicHeight()) {
- notifyHeightChanged(true /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ true);
}
}
@@ -2623,7 +2621,7 @@
if (mIsSummaryWithChildren) {
mChildrenContainer.onExpansionChanged();
}
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
}
updateShelfIconColor();
}
@@ -2661,7 +2659,7 @@
if (expand != mIsSystemExpanded) {
final boolean wasExpanded = isExpanded();
mIsSystemExpanded = expand;
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
onExpansionChanged(false /* userAction */, wasExpanded);
if (mIsSummaryWithChildren) {
mChildrenContainer.updateGroupOverflow();
@@ -2680,7 +2678,7 @@
if (mIsSummaryWithChildren) {
mChildrenContainer.updateGroupOverflow();
}
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
}
if (isAboveShelf() != wasAboveShelf) {
mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
@@ -2837,7 +2835,7 @@
super.onLayout(changed, left, top, right, bottom);
if (intrinsicBefore != getIntrinsicHeight()
&& (intrinsicBefore != 0 || getActualHeight() > 0)) {
- notifyHeightChanged(true /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ true);
}
if (mMenuRow != null && mMenuRow.getMenuView() != null) {
mMenuRow.onParentHeightUpdate();
@@ -2880,8 +2878,7 @@
mSensitiveHiddenInGeneral = hideSensitive;
int intrinsicAfter = getIntrinsicHeight();
if (intrinsicBefore != intrinsicAfter) {
- boolean needsAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
- notifyHeightChanged(needsAnimation);
+ notifyHeightChanged(/* needsAnimation= */ true);
}
}
@@ -3018,7 +3015,7 @@
if (isChildInGroup()) {
mGroupExpansionManager.setGroupExpanded(mEntry, true);
}
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
}
public void setChildrenExpanded(boolean expanded, boolean animate) {
@@ -3241,13 +3238,8 @@
mGuts.setActualHeight(height);
return;
}
- int contentHeight = Math.max(getMinHeight(), height);
for (NotificationContentView l : mLayouts) {
- if (mInlineReplyAnimation.isEnabled()) {
- l.setContentHeight(height);
- } else {
- l.setContentHeight(contentHeight);
- }
+ l.setContentHeight(height);
}
if (mIsSummaryWithChildren) {
mChildrenContainer.setActualHeight(height);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 137e1b2..8a3e7e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -699,8 +699,7 @@
int hint;
if (mHeadsUpChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_HEADSUP)) {
hint = getViewHeight(VISIBLE_TYPE_HEADSUP);
- if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isAnimatingAppearance()
- && mHeadsUpRemoteInputController.isFocusAnimationFlagActive()) {
+ if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isAnimatingAppearance()) {
// While the RemoteInputView is animating its appearance, it should be allowed
// to overlap the hint, therefore no space is reserved for the hint during the
// appearance animation of the RemoteInputView
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 14593cb..e141b7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.row;
+import static android.app.Notification.EXTRA_BUILDER_APPLICATION_INFO;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
@@ -353,18 +354,15 @@
}
private void bindPackage() {
- ApplicationInfo info;
- try {
- info = mPm.getApplicationInfo(
- mPackageName,
- PackageManager.MATCH_UNINSTALLED_PACKAGES
- | PackageManager.MATCH_DISABLED_COMPONENTS
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
- | PackageManager.MATCH_DIRECT_BOOT_AWARE);
- if (info != null) {
+ // filled in if missing during notification inflation, which must have happened if
+ // we have a notification to long press on
+ ApplicationInfo info =
+ mSbn.getNotification().extras.getParcelable(EXTRA_BUILDER_APPLICATION_INFO,
+ ApplicationInfo.class);
+ if (info != null) {
+ try {
mAppName = String.valueOf(mPm.getApplicationLabel(info));
- }
- } catch (PackageManager.NameNotFoundException e) {
+ } catch (Exception ignored) {}
}
((TextView) findViewById(R.id.pkg_name)).setText(mAppName);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index d8ebd42..2012099 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.row;
+import static android.app.Notification.EXTRA_BUILDER_APPLICATION_INFO;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
@@ -66,7 +67,6 @@
import java.lang.annotation.Retention;
import java.util.List;
-import java.util.Set;
/**
* The guts of a notification revealed when performing a long press.
@@ -297,19 +297,18 @@
private void bindHeader() {
// Package name
mPkgIcon = null;
- ApplicationInfo info;
- try {
- info = mPm.getApplicationInfo(
- mPackageName,
- PackageManager.MATCH_UNINSTALLED_PACKAGES
- | PackageManager.MATCH_DISABLED_COMPONENTS
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
- | PackageManager.MATCH_DIRECT_BOOT_AWARE);
- if (info != null) {
+ // filled in if missing during notification inflation, which must have happened if
+ // we have a notification to long press on
+ ApplicationInfo info =
+ mSbn.getNotification().extras.getParcelable(EXTRA_BUILDER_APPLICATION_INFO,
+ ApplicationInfo.class);
+ if (info != null) {
+ try {
mAppName = String.valueOf(mPm.getApplicationLabel(info));
mPkgIcon = mPm.getApplicationIcon(info);
- }
- } catch (PackageManager.NameNotFoundException e) {
+ } catch (Exception ignored) {}
+ }
+ if (mPkgIcon == null) {
// app is gone, just show package name and generic icon
mPkgIcon = mPm.getDefaultActivityIcon();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index ab2f664..ac44b3e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -262,9 +262,6 @@
return mStackHeight;
}
- /** Tracks the state from HeadsUpManager#hasNotifications() */
- private boolean mHasHeadsUpEntries;
-
@Inject
public AmbientState(
@NonNull Context context,
@@ -547,10 +544,6 @@
mPanelTracking = panelTracking;
}
- public boolean hasPulsingNotifications() {
- return mPulsing && mHasHeadsUpEntries;
- }
-
public void setPulsing(boolean hasPulsing) {
mPulsing = hasPulsing;
}
@@ -701,10 +694,6 @@
return mAppearFraction;
}
- public void setHasHeadsUpEntries(boolean hasHeadsUpEntries) {
- mHasHeadsUpEntries = hasHeadsUpEntries;
- }
-
public void setStackTopMargin(int stackTopMargin) {
mStackTopMargin = stackTopMargin;
}
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 b47b18d..77e9425 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
@@ -87,7 +87,6 @@
import com.android.systemui.ExpandHelper;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
-import com.android.systemui.flags.RefactorFlag;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.res.R;
@@ -197,8 +196,6 @@
*/
private Set<Integer> mDebugTextUsedYPositions;
private final boolean mDebugRemoveAnimation;
- private final boolean mSensitiveRevealAnimEndabled;
- private final RefactorFlag mAnimatedInsets;
private int mContentHeight;
private float mIntrinsicContentHeight;
private int mPaddingBetweenElements;
@@ -619,9 +616,6 @@
Flags.LOCKSCREEN_ENABLE_LANDSCAPE);
mDebugLines = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_LINES);
mDebugRemoveAnimation = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
- mSensitiveRevealAnimEndabled = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
- mAnimatedInsets =
- new RefactorFlag(mFeatureFlags, Flags.ANIMATED_NOTIFICATION_SHADE_INSETS);
mSectionsManager = Dependency.get(NotificationSectionsManager.class);
mScreenOffAnimationController =
Dependency.get(ScreenOffAnimationController.class);
@@ -656,9 +650,7 @@
mGroupMembershipManager = Dependency.get(GroupMembershipManager.class);
mGroupExpansionManager = Dependency.get(GroupExpansionManager.class);
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
- if (mAnimatedInsets.isEnabled()) {
- setWindowInsetsAnimationCallback(mInsetsCallback);
- }
+ setWindowInsetsAnimationCallback(mInsetsCallback);
}
/**
@@ -928,12 +920,11 @@
}
void updateSidePadding(int viewWidth) {
- final boolean portrait =
- getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
+ final int orientation = getResources().getConfiguration().orientation;
mLastUpdateSidePaddingDumpString = "viewWidth=" + viewWidth
+ " skinnyNotifsInLandscape=" + mSkinnyNotifsInLandscape
- + " portrait=" + portrait;
+ + " orientation=" + orientation;
if (DEBUG_UPDATE_SIDE_PADDING) {
Log.v(TAG, "updateSidePadding: " + mLastUpdateSidePaddingDumpString);
@@ -945,7 +936,7 @@
}
// Portrait is easy, just use the dimen for paddings
- if (portrait) {
+ if (orientation == Configuration.ORIENTATION_PORTRAIT) {
mSidePaddings = mMinimumPaddings;
return;
}
@@ -1735,11 +1726,7 @@
return;
}
mForcedScroll = v;
- if (mAnimatedInsets.isEnabled()) {
- updateForcedScroll();
- } else {
- scrollTo(v);
- }
+ updateForcedScroll();
}
public boolean scrollTo(View v) {
@@ -1784,31 +1771,15 @@
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- if (!mAnimatedInsets.isEnabled()) {
- mBottomInset = insets.getInsets(WindowInsets.Type.ime()).bottom;
- }
mWaterfallTopInset = 0;
final DisplayCutout cutout = insets.getDisplayCutout();
if (cutout != null) {
mWaterfallTopInset = cutout.getWaterfallInsets().top;
}
- if (mAnimatedInsets.isEnabled() && !mIsInsetAnimationRunning) {
+ if (!mIsInsetAnimationRunning) {
// update bottom inset e.g. after rotation
updateBottomInset(insets);
}
- if (!mAnimatedInsets.isEnabled()) {
- int range = getScrollRange();
- if (mOwnScrollY > range) {
- // HACK: We're repeatedly getting staggered insets here while the IME is
- // animating away. To work around that we'll wait until things have settled.
- removeCallbacks(mReclamp);
- postDelayed(mReclamp, 50);
- } else if (mForcedScroll != null) {
- // The scroll was requested before we got the actual inset - in case we need
- // to scroll up some more do so now.
- scrollTo(mForcedScroll);
- }
- }
return insets;
}
@@ -2577,7 +2548,7 @@
return;
}
child.setOnHeightChangedListener(null);
- if (child instanceof ExpandableNotificationRow && mSensitiveRevealAnimEndabled) {
+ if (child instanceof ExpandableNotificationRow) {
NotificationEntry entry = ((ExpandableNotificationRow) child).getEntry();
entry.removeOnSensitivityChangedListener(mOnChildSensitivityChangedListener);
}
@@ -2873,7 +2844,7 @@
private void onViewAddedInternal(ExpandableView child) {
updateHideSensitiveForChild(child);
child.setOnHeightChangedListener(mOnChildHeightChangedListener);
- if (child instanceof ExpandableNotificationRow && mSensitiveRevealAnimEndabled) {
+ if (child instanceof ExpandableNotificationRow) {
NotificationEntry entry = ((ExpandableNotificationRow) child).getEntry();
entry.addOnSensitivityChangedListener(mOnChildSensitivityChangedListener);
}
@@ -5379,13 +5350,6 @@
mTopHeadsUpRow = topHeadsUpRow;
}
- /**
- * @param numHeadsUp the number of active alerting notifications.
- */
- public void setNumHeadsUp(long numHeadsUp) {
- mAmbientState.setHasHeadsUpEntries(numHeadsUp > 0);
- }
-
public boolean getIsExpanded() {
return mIsExpanded;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 3bdd0e9..6553193 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -363,7 +363,8 @@
};
private NotifStats mNotifStats = NotifStats.getEmpty();
- private float mMaxAlphaForExpansion = 1.0f;
+ private float mMaxAlphaForKeyguard = 1.0f;
+ private String mMaxAlphaForKeyguardSource = "constructor";
private float mMaxAlphaForUnhide = 1.0f;
/**
@@ -689,9 +690,7 @@
@Override
public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
- long numEntries = mHeadsUpManager.getAllEntries().count();
NotificationEntry topEntry = mHeadsUpManager.getTopEntry();
- mView.setNumHeadsUp(numEntries);
mView.setTopHeadsUpRow(topEntry != null ? topEntry.getRow() : null);
generateHeadsUpAnimation(entry, isHeadsUp);
}
@@ -1322,9 +1321,14 @@
return mView.getEmptyShadeViewHeight();
}
- public void setMaxAlphaForExpansion(float alpha) {
- mMaxAlphaForExpansion = alpha;
+ /** Set the max alpha for keyguard */
+ public void setMaxAlphaForKeyguard(float alpha, String source) {
+ mMaxAlphaForKeyguard = alpha;
+ mMaxAlphaForKeyguardSource = source;
updateAlpha();
+ if (DEBUG) {
+ Log.d(TAG, "setMaxAlphaForKeyguard=" + alpha + " --- from: " + source);
+ }
}
private void setMaxAlphaForUnhide(float alpha) {
@@ -1343,7 +1347,7 @@
private void updateAlpha() {
if (mView != null) {
- mView.setAlpha(Math.min(mMaxAlphaForExpansion,
+ mView.setAlpha(Math.min(mMaxAlphaForKeyguard,
Math.min(mMaxAlphaForUnhide, mMaxAlphaForGlanceableHub)));
}
}
@@ -1833,9 +1837,10 @@
@Override
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
- pw.println("mMaxAlphaForExpansion=" + mMaxAlphaForExpansion);
pw.println("mMaxAlphaForUnhide=" + mMaxAlphaForUnhide);
pw.println("mMaxAlphaForGlanceableHub=" + mMaxAlphaForGlanceableHub);
+ pw.println("mMaxAlphaForKeyguard=" + mMaxAlphaForKeyguard);
+ pw.println("mMaxAlphaForKeyguardSource=" + mMaxAlphaForKeyguardSource);
}
/**
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 76495cb..8b1b06e 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
@@ -66,15 +66,18 @@
}
launch {
+ var wasExpanding = false
viewModel.expandFraction.collect { expandFraction ->
+ val nowExpanding = expandFraction != 0f && expandFraction != 1f
+ if (nowExpanding && !wasExpanding) {
+ controller.onExpansionStarted()
+ }
ambientState.expansionFraction = expandFraction
controller.expandedHeight = expandFraction * controller.view.height
- controller.setMaxAlphaForExpansion(
- ((expandFraction - 0.5f) / 0.5f).coerceAtLeast(0f)
- )
- if (expandFraction == 0f || expandFraction == 1f) {
+ if (!nowExpanding && wasExpanding) {
controller.onExpansionStopped()
}
+ wasExpanding = nowExpanding
}
}
}
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 566c030..ece7a7f 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
@@ -106,27 +106,26 @@
val disposableHandleMainImmediate =
view.repeatWhenAttached(mainImmediateDispatcher) {
repeatOnLifecycle(Lifecycle.State.CREATED) {
- if (!sceneContainerFlags.flexiNotifsEnabled()) {
- launch {
- // Only temporarily needed, until flexi notifs go live
- viewModel.shadeCollapseFadeIn.collect { fadeIn ->
- if (fadeIn) {
- android.animation.ValueAnimator.ofFloat(0f, 1f).apply {
- duration = 250
- addUpdateListener { animation ->
- controller.setMaxAlphaForExpansion(
- animation.getAnimatedFraction()
- )
- }
- addListener(
- object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- viewModel.setShadeCollapseFadeInComplete(true)
- }
- }
+ launch {
+ // Only temporarily needed, until flexi notifs go live
+ viewModel.shadeCollapseFadeIn.collect { fadeIn ->
+ if (fadeIn) {
+ android.animation.ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = 250
+ addUpdateListener { animation ->
+ controller.setMaxAlphaForKeyguard(
+ animation.animatedFraction,
+ "SharedNotificationContainerVB (collapseFadeIn)"
)
- start()
}
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ viewModel.setShadeCollapseFadeInComplete(true)
+ }
+ }
+ )
+ start()
}
}
}
@@ -164,13 +163,12 @@
launch { viewModel.translationX.collect { x -> controller.translationX = x } }
- if (!sceneContainerFlags.isEnabled()) {
- launch {
- viewModel.expansionAlpha(viewState).collect {
- controller.setMaxAlphaForExpansion(it)
- }
+ launch {
+ viewModel.keyguardAlpha(viewState).collect {
+ controller.setMaxAlphaForKeyguard(it, "SharedNotificationContainerVB")
}
}
+
launch {
viewModel.glanceableHubAlpha.collect {
controller.setMaxAlphaForGlanceableHub(it)
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 78fc147..5e87a7a 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
@@ -20,7 +20,6 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
import com.android.systemui.common.shared.model.NotificationContainerBounds
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
@@ -37,8 +36,6 @@
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
-import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
-import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
@@ -97,7 +94,6 @@
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val shadeInteractor: ShadeInteractor,
- communalInteractor: CommunalInteractor,
private val alternateBouncerToGoneTransitionViewModel:
AlternateBouncerToGoneTransitionViewModel,
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
@@ -127,22 +123,6 @@
private val statesForConstrainedNotifications: Set<KeyguardState> =
setOf(AOD, LOCKSCREEN, DOZING, ALTERNATE_BOUNCER, PRIMARY_BOUNCER)
- private val lockscreenToGlanceableHubRunning =
- keyguardTransitionInteractor
- .transition(LOCKSCREEN, GLANCEABLE_HUB)
- .map { it.transitionState == STARTED || it.transitionState == RUNNING }
- .distinctUntilChanged()
- .onStart { emit(false) }
- .dumpWhileCollecting("lockscreenToGlanceableHubRunning")
-
- private val glanceableHubToLockscreenRunning =
- keyguardTransitionInteractor
- .transition(GLANCEABLE_HUB, LOCKSCREEN)
- .map { it.transitionState == STARTED || it.transitionState == RUNNING }
- .distinctUntilChanged()
- .onStart { emit(false) }
- .dumpWhileCollecting("glanceableHubToLockscreenRunning")
-
/**
* Shade locked is a legacy concept, but necessary to mimic current functionality. Listen for
* both SHADE_LOCKED and shade/qs expansion in order to determine lock state, as one can arrive
@@ -218,21 +198,38 @@
)
.dumpValue("isOnLockscreenWithoutShade")
+ /** If the user is visually on the glanceable hub or transitioning to/from it */
+ private val isOnGlanceableHub: Flow<Boolean> =
+ combine(
+ keyguardTransitionInteractor.finishedKeyguardState.map { state ->
+ state == GLANCEABLE_HUB
+ },
+ keyguardTransitionInteractor
+ .isInTransitionWhere { from, to ->
+ from == GLANCEABLE_HUB || to == GLANCEABLE_HUB
+ }
+ .onStart { emit(false) }
+ ) { isOnGlanceableHub, transitioningToOrFromHub ->
+ isOnGlanceableHub || transitioningToOrFromHub
+ }
+ .distinctUntilChanged()
+ .dumpWhileCollecting("isOnGlanceableHub")
+
/** Are we purely on the glanceable hub without the shade/qs? */
val isOnGlanceableHubWithoutShade: Flow<Boolean> =
combine(
- communalInteractor.isIdleOnCommunal,
+ isOnGlanceableHub,
// Shade with notifications
shadeInteractor.shadeExpansion.map { it > 0f },
// Shade without notifications, quick settings only (pull down from very top on
// lockscreen)
shadeInteractor.qsExpansion.map { it > 0f },
- ) { isIdleOnCommunal, isShadeVisible, qsExpansion ->
- isIdleOnCommunal && !(isShadeVisible || qsExpansion)
+ ) { isGlanceableHub, isShadeVisible, qsExpansion ->
+ isGlanceableHub && !(isShadeVisible || qsExpansion)
}
.stateIn(
scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
+ started = SharingStarted.Eagerly,
initialValue = false,
)
.dumpWhileCollecting("isOnGlanceableHubWithoutShade")
@@ -353,6 +350,9 @@
if (shadeExpansion > 0f || qsExpansion > 0f) {
if (configurationBasedDimensions.useSplitShade) {
emit(1f)
+ } else if (qsExpansion == 1f) {
+ // Ensure HUNs will be visible in QS shade (at least while unlocked)
+ emit(1f)
} else {
// Fade as QS shade expands
emit(1f - qsExpansion)
@@ -376,7 +376,7 @@
}
.dumpWhileCollecting("alphaWhenGoneAndShadeState")
- fun expansionAlpha(viewState: ViewStateAccessor): Flow<Float> {
+ fun keyguardAlpha(viewState: ViewStateAccessor): Flow<Float> {
// All transition view models are mututally exclusive, and safe to merge
val alphaTransitions =
merge(
@@ -427,43 +427,39 @@
},
)
.distinctUntilChanged()
- .dumpWhileCollecting("expansionAlpha")
+ .dumpWhileCollecting("keyguardAlpha")
}
/**
- * Returns a flow of the expected alpha while running a LOCKSCREEN<->GLANCEABLE_HUB transition
- * or idle on the glanceable hub.
+ * Returns a flow of the expected alpha while running a LOCKSCREEN<->GLANCEABLE_HUB or
+ * DREAMING<->GLANCEABLE_HUB transition or idle on the hub.
*
* Must return 1.0f when not controlling the alpha since notifications does a min of all the
* alpha sources.
*/
val glanceableHubAlpha: Flow<Float> =
- isOnGlanceableHubWithoutShade
- .flatMapLatest { isOnGlanceableHubWithoutShade ->
- combineTransform(
- lockscreenToGlanceableHubRunning,
- glanceableHubToLockscreenRunning,
- merge(
- lockscreenToGlanceableHubTransitionViewModel.notificationAlpha,
- glanceableHubToLockscreenTransitionViewModel.notificationAlpha,
- )
- ) { lockscreenToGlanceableHubRunning, glanceableHubToLockscreenRunning, alpha ->
- if (isOnGlanceableHubWithoutShade) {
- // Notifications should not be visible on the glanceable hub.
- // TODO(b/321075734): implement a way to actually set the notifications to
- // gone
- // while on the hub instead of just adjusting alpha
- emit(0f)
- } else if (
- lockscreenToGlanceableHubRunning || glanceableHubToLockscreenRunning
- ) {
- emit(alpha)
- } else {
- // Not on the hub and no transitions running, return full visibility so we
- // don't
- // block the notifications from showing.
- emit(1f)
- }
+ combineTransform(
+ isOnGlanceableHubWithoutShade,
+ isOnLockscreen,
+ merge(
+ lockscreenToGlanceableHubTransitionViewModel.notificationAlpha,
+ glanceableHubToLockscreenTransitionViewModel.notificationAlpha,
+ )
+ ) { isOnGlanceableHubWithoutShade, isOnLockscreen, alpha,
+ ->
+ if (isOnGlanceableHubWithoutShade && !isOnLockscreen) {
+ // Notifications should not be visible on the glanceable hub.
+ // TODO(b/321075734): implement a way to actually set the notifications to
+ // gone while on the hub instead of just adjusting alpha
+ emit(0f)
+ } else if (isOnGlanceableHubWithoutShade) {
+ // We are transitioning between hub and lockscreen, so set the alpha for the
+ // transition animation.
+ emit(alpha)
+ } else {
+ // Not on the hub and no transitions running, return full visibility so we
+ // don't block the notifications from showing.
+ emit(1f)
}
}
.dumpWhileCollecting("glanceableHubAlpha")
@@ -474,7 +470,10 @@
*/
fun translationY(params: BurnInParameters): Flow<Float> {
return combine(
- aodBurnInViewModel.translationY(params).onStart { emit(0f) },
+ aodBurnInViewModel
+ .movement(params)
+ .map { it.translationY.toFloat() }
+ .onStart { emit(0f) },
isOnLockscreenWithoutShade,
merge(
keyguardInteractor.keyguardTranslationY,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 23a080b..a55de25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -648,6 +648,10 @@
}
if (intent.isActivity) {
assistManagerLazy.get().hideAssist()
+ // This activity could have started while the device is dreaming, in which case
+ // the dream would occlude the activity. In order to show the newly started
+ // activity, we wake from the dream.
+ keyguardUpdateMonitor.awakenFromDream()
}
intentSentUiThreadCallback?.let { postOnUiThread(runnable = it) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 48d3157..ab9ecab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -301,8 +301,7 @@
if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP == key.getKeyCode()) {
mMetricsLogger.action(MetricsEvent.ACTION_SYSTEM_NAVIGATION_KEY_UP);
- mShadeViewController.collapse(
- false /* delayed */, 1.0f /* speedUpFactor */);
+ mShadeController.animateCollapseShade();
} else if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN == key.getKeyCode()) {
mMetricsLogger.action(MetricsEvent.ACTION_SYSTEM_NAVIGATION_KEY_DOWN);
if (mShadeViewController.isFullyCollapsed()) {
@@ -314,7 +313,7 @@
mHeadsUpManager.unpinAll(true /* userUnpinned */);
mMetricsLogger.count("panel_open", 1);
} else if (!mQsController.getExpanded()
- && !mShadeViewController.isExpandingOrCollapsing()) {
+ && !mShadeController.isExpandingOrCollapsing()) {
mShadeController.animateExpandQs();
mMetricsLogger.count("panel_open_qs", 1);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index db15144..d32e88b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2790,6 +2790,9 @@
|| mKeyguardStateController.isKeyguardGoingAway()
|| mKeyguardViewMediator.requestedShowSurfaceBehindKeyguard()
|| mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind());
+ boolean dreaming =
+ mKeyguardStateController.isShowing() && mKeyguardUpdateMonitor.isDreaming()
+ && !unlocking;
mScrimController.setExpansionAffectsAlpha(!unlocking);
@@ -2834,13 +2837,16 @@
// this as otherwise it can remain pending and leave keyguard in a weird state.
mUnlockScrimCallback.onCancelled();
} else if (mIsIdleOnCommunal) {
- mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB);
+ if (dreaming) {
+ mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
+ } else {
+ mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB);
+ }
} else if (mKeyguardStateController.isShowing()
&& !mKeyguardStateController.isOccluded()
&& !unlocking) {
mScrimController.transitionTo(ScrimState.KEYGUARD);
- } else if (mKeyguardStateController.isShowing() && mKeyguardUpdateMonitor.isDreaming()
- && !unlocking) {
+ } else if (dreaming) {
mScrimController.transitionTo(ScrimState.DREAMING);
} else {
mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
@@ -3043,8 +3049,7 @@
if (userSetup != mUserSetup) {
mUserSetup = userSetup;
if (!mUserSetup && mState == StatusBarState.SHADE) {
- mShadeSurface.collapse(true /* animate */, false /* delayed */,
- 1.0f /* speedUpFactor */);
+ mShadeController.animateCollapseShade();
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index a155e94..24be3db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -43,6 +43,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.AnimationStateHandler;
+import com.android.systemui.statusbar.policy.AvalancheController;
import com.android.systemui.statusbar.policy.BaseHeadsUpManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
@@ -124,9 +125,10 @@
AccessibilityManagerWrapper accessibilityManagerWrapper,
UiEventLogger uiEventLogger,
JavaAdapter javaAdapter,
- ShadeInteractor shadeInteractor) {
+ ShadeInteractor shadeInteractor,
+ AvalancheController avalancheController) {
super(context, logger, handler, globalSettings, systemClock, executor,
- accessibilityManagerWrapper, uiEventLogger);
+ accessibilityManagerWrapper, uiEventLogger, avalancheController);
Resources resources = mContext.getResources();
mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
statusBarStateController.addCallback(mStatusBarStateListener);
@@ -279,7 +281,7 @@
if (headsUpEntry != null && headsUpEntry.mRemoteInputActive != remoteInputActive) {
headsUpEntry.mRemoteInputActive = remoteInputActive;
if (remoteInputActive) {
- headsUpEntry.removeAutoRemovalCallbacks("setRemoteInputActive(true)");
+ headsUpEntry.cancelAutoRemovalCallbacks("setRemoteInputActive(true)");
} else {
headsUpEntry.updateEntry(false /* updatePostTime */, "setRemoteInputActive(false)");
}
@@ -482,7 +484,7 @@
this.mExpanded = expanded;
if (expanded) {
- removeAutoRemovalCallbacks("setExpanded(true)");
+ cancelAutoRemovalCallbacks("setExpanded(true)");
} else {
updateEntry(false /* updatePostTime */, "setExpanded(false)");
}
@@ -495,7 +497,7 @@
mGutsShownPinned = gutsShownPinned;
if (gutsShownPinned) {
- removeAutoRemovalCallbacks("setGutsShownPinned(true)");
+ cancelAutoRemovalCallbacks("setGutsShownPinned(true)");
} else {
updateEntry(false /* updatePostTime */, "setGutsShownPinned(false)");
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 088f894..02293a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -536,6 +536,16 @@
mAnimateChange = state.getAnimateChange();
mAnimationDuration = state.getAnimationDuration();
+ if (mState == ScrimState.GLANCEABLE_HUB_OVER_DREAM) {
+ // When the device is docked while on GLANCEABLE_HUB, the dream starts underneath the
+ // hub and the ScrimState transitions to GLANCEABLE_HUB_OVER_DREAM. To prevent the
+ // scrims from flickering in during this transition, we set the panel expansion
+ // fraction, which is 1 when idle on GLANCEABLE_HUB, to 0. This only occurs when the hub
+ // is open because the hub lives in the same window as the shade, which is not visible
+ // when transitioning from KEYGUARD to DREAMING.
+ mPanelExpansionFraction = 0f;
+ }
+
applyState();
mScrimInFront.setBlendWithMainColor(state.shouldBlendWithMainColor());
@@ -738,6 +748,7 @@
boolean relevantState = (mState == ScrimState.UNLOCKED
|| mState == ScrimState.KEYGUARD
|| mState == ScrimState.DREAMING
+ || mState == ScrimState.GLANCEABLE_HUB_OVER_DREAM
|| mState == ScrimState.SHADE_LOCKED
|| mState == ScrimState.PULSING);
if (!(relevantState && mExpansionAffectsAlpha) || mAnimatingPanelExpansionOnUnlock) {
@@ -844,7 +855,8 @@
return;
}
mBouncerHiddenFraction = bouncerHiddenAmount;
- if (mState == ScrimState.DREAMING || mState == ScrimState.GLANCEABLE_HUB) {
+ if (mState == ScrimState.DREAMING || mState == ScrimState.GLANCEABLE_HUB
+ || mState == ScrimState.GLANCEABLE_HUB_OVER_DREAM) {
// The dreaming and glanceable hub states requires this for the scrim calculation, so we
// should only trigger an update in those states.
applyAndDispatchState();
@@ -926,7 +938,8 @@
return;
}
- if (mState == ScrimState.UNLOCKED || mState == ScrimState.DREAMING) {
+ if (mState == ScrimState.UNLOCKED || mState == ScrimState.DREAMING
+ || mState == ScrimState.GLANCEABLE_HUB_OVER_DREAM) {
final boolean occluding =
mOccludeAnimationPlaying || mState.mLaunchingAffordanceWithPreview;
// Darken scrim as it's pulled down while unlocked. If we're unlocked but playing the
@@ -954,8 +967,9 @@
mInFrontAlpha = 0;
}
- if (mState == ScrimState.DREAMING
+ if ((mState == ScrimState.DREAMING || mState == ScrimState.GLANCEABLE_HUB_OVER_DREAM)
&& mBouncerHiddenFraction != KeyguardBouncerConstants.EXPANSION_HIDDEN) {
+ // Bouncer is opening over dream or glanceable hub over dream.
final float interpolatedFraction =
BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(
mBouncerHiddenFraction);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index f2a649b..712f65d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -299,9 +299,9 @@
},
/**
- * Device is locked or on dream and user has swiped from the right edge to enter the glanceable
- * hub UI. From this state, the user can swipe from the left edge to go back to the lock screen
- * or dream, as well as swipe down for the notifications and up for the bouncer.
+ * Device is on the lockscreen and user has swiped from the right edge to enter the glanceable
+ * hub UI. From this state, the user can swipe from the left edge to go back to the lock screen,
+ * as well as swipe down for the notifications and up for the bouncer.
*/
GLANCEABLE_HUB {
@Override
@@ -310,6 +310,32 @@
mBehindAlpha = 0;
mNotifAlpha = 0;
mFrontAlpha = 0;
+
+ mFrontTint = Color.TRANSPARENT;
+ mBehindTint = mBackgroundColor;
+ mNotifTint = mClipQsScrim ? mBackgroundColor : Color.TRANSPARENT;
+ }
+ },
+
+ /**
+ * Device is dreaming and user has swiped from the right edge to enter the glanceable hub UI.
+ * From this state, the user can swipe from the left edge to go back to the dream, as well as
+ * swipe down for the notifications and up for the bouncer.
+ *
+ * This is a separate state from {@link #GLANCEABLE_HUB} because the scrims behave differently
+ * when the dream is running.
+ */
+ GLANCEABLE_HUB_OVER_DREAM {
+ @Override
+ public void prepare(ScrimState previousState) {
+ // No scrims should be visible by default in this state.
+ mBehindAlpha = 0;
+ mNotifAlpha = 0;
+ mFrontAlpha = 0;
+
+ mFrontTint = Color.TRANSPARENT;
+ mBehindTint = mBackgroundColor;
+ mNotifTint = mClipQsScrim ? mBackgroundColor : Color.TRANSPARENT;
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index ba89d4a..7dd328a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -579,7 +579,7 @@
final boolean hideBouncerOverDream =
mDreamOverlayStateController.isOverlayActive()
&& (mShadeLockscreenInteractor.isExpanded()
- || mShadeLockscreenInteractor.isExpandingOrCollapsing());
+ || mShadeController.get().isExpandingOrCollapsing());
final boolean isUserTrackingStarted =
event.getFraction() != EXPANSION_HIDDEN && event.getTracking();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 8e9c038..5b14259 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -44,6 +44,7 @@
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -87,6 +88,7 @@
private final NotificationMediaManager mMediaManager;
private final NotificationGutsManager mGutsManager;
private final ShadeViewController mNotificationPanel;
+ private final PanelExpansionInteractor mPanelExpansionInteractor;
private final HeadsUpManager mHeadsUpManager;
private final AboveShelfObserver mAboveShelfObserver;
private final DozeScrimController mDozeScrimController;
@@ -108,6 +110,7 @@
StatusBarNotificationPresenter(
Context context,
ShadeViewController panel,
+ PanelExpansionInteractor panelExpansionInteractor,
QuickSettingsController quickSettingsController,
HeadsUpManager headsUp,
NotificationShadeWindowView statusBarWindow,
@@ -134,6 +137,7 @@
mActivityStarter = activityStarter;
mKeyguardStateController = keyguardStateController;
mNotificationPanel = panel;
+ mPanelExpansionInteractor = panelExpansionInteractor;
mQsController = quickSettingsController;
mHeadsUpManager = headsUp;
mDynamicPrivacyController = dynamicPrivacyController;
@@ -202,7 +206,7 @@
@Override
public boolean isCollapsing() {
- return mNotificationPanel.isCollapsing()
+ return mPanelExpansionInteractor.isCollapsing()
|| mNotificationShadeWindowController.isLaunchingActivity();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 223eaf7..88374d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -32,6 +32,7 @@
import com.android.systemui.statusbar.notification.stack.AnimationProperties
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.Flags.lightRevealMigration
import com.android.app.tracing.namedRunnable
import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
@@ -45,7 +46,7 @@
/**
* Duration for the light reveal portion of the animation.
*/
-private const val LIGHT_REVEAL_ANIMATION_DURATION = 750L
+private const val LIGHT_REVEAL_ANIMATION_DURATION = 500L
/**
* Controller for the unlocked screen off animation, which runs when the device is going to sleep
@@ -66,7 +67,7 @@
private val notifShadeWindowControllerLazy: dagger.Lazy<NotificationShadeWindowController>,
private val interactionJankMonitor: InteractionJankMonitor,
private val powerManager: PowerManager,
- private val handler: Handler = Handler(),
+ private val handler: Handler = Handler()
) : WakefulnessLifecycle.Observer, ScreenOffAnimation {
private lateinit var centralSurfaces: CentralSurfaces
private lateinit var shadeViewController: ShadeViewController
@@ -95,6 +96,7 @@
duration = LIGHT_REVEAL_ANIMATION_DURATION
interpolator = Interpolators.LINEAR
addUpdateListener {
+ if (lightRevealMigration()) return@addUpdateListener
if (lightRevealScrim.revealEffect !is CircleReveal) {
lightRevealScrim.revealAmount = it.animatedValue as Float
}
@@ -107,6 +109,7 @@
}
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationCancel(animation: Animator) {
+ if (lightRevealMigration()) return
if (lightRevealScrim.revealEffect !is CircleReveal) {
lightRevealScrim.revealAmount = 1f
}
@@ -371,7 +374,7 @@
* AOD UI.
*/
override fun isAnimationPlaying(): Boolean {
- return lightRevealAnimationPlaying || aodUiAnimationPlaying
+ return isScreenOffLightRevealAnimationPlaying() || aodUiAnimationPlaying
}
override fun shouldAnimateInKeyguard(): Boolean =
@@ -395,6 +398,9 @@
/**
* Whether the light reveal animation is playing. The second part of the screen off animation,
* where AOD animates in, might still be playing if this returns false.
+ *
+ * Note: This only refers to the specific light reveal animation that is playing during lock
+ * therefore LightRevealScrimInteractor.isAnimating is not the desired response.
*/
fun isScreenOffLightRevealAnimationPlaying(): Boolean {
return lightRevealAnimationPlaying
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt
index efdce06..016ba5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt
@@ -55,7 +55,7 @@
@Inject
constructor(
broadcastDispatcher: BroadcastDispatcher,
- private val carrierConfigManager: CarrierConfigManager,
+ private val carrierConfigManager: CarrierConfigManager?,
dumpManager: DumpManager,
logger: MobileInputLogger,
@Application scope: CoroutineScope,
@@ -87,7 +87,7 @@
.onEach { logger.logCarrierConfigChanged(it) }
.filter { SubscriptionManager.isValidSubscriptionId(it) }
.mapNotNull { subId ->
- val config = carrierConfigManager.getConfigForSubId(subId)
+ val config = carrierConfigManager?.getConfigForSubId(subId)
config?.let { subId to it }
}
.shareIn(scope, SharingStarted.WhileSubscribed())
@@ -111,7 +111,7 @@
fun getOrCreateConfigForSubId(subId: Int): SystemUiCarrierConfig {
return configs.getOrElse(subId) {
val config = SystemUiCarrierConfig(subId, defaultConfig)
- val carrierConfig = carrierConfigManager.getConfigForSubId(subId)
+ val carrierConfig = carrierConfigManager?.getConfigForSubId(subId)
if (carrierConfig != null) config.processNewCarrierConfig(carrierConfig)
configs.put(subId, config)
config
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
new file mode 100644
index 0000000..6aaf5d6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.policy
+
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
+import com.android.systemui.statusbar.policy.BaseHeadsUpManager.HeadsUpEntry
+import javax.inject.Inject
+
+/*
+ * Control when heads up notifications show during an avalanche where notifications arrive in fast
+ * succession, by delaying visual listener side effects and removal handling from BaseHeadsUpManager
+ */
+@SysUISingleton
+class AvalancheController @Inject constructor() {
+
+ private val tag = "AvalancheController"
+ private val debug = false
+
+ // HUN showing right now, in the floating state where full shade is hidden, on launcher or AOD
+ @VisibleForTesting var headsUpEntryShowing: HeadsUpEntry? = null
+
+ // List of runnables to run for the HUN showing right now
+ private var headsUpEntryShowingRunnableList: MutableList<Runnable> = ArrayList()
+
+ // HeadsUpEntry waiting to show
+ // Use sortable list instead of priority queue for debugging
+ private val nextList: MutableList<HeadsUpEntry> = ArrayList()
+
+ // Map of HeadsUpEntry waiting to show, and runnables to run when it shows.
+ // Use HashMap instead of SortedMap for faster lookup, and also because the ordering
+ // provided by HeadsUpEntry.compareTo is not consistent over time or with HeadsUpEntry.equals
+ @VisibleForTesting var nextMap: MutableMap<HeadsUpEntry, MutableList<Runnable>> = HashMap()
+
+ // Map of Runnable to label for debugging only
+ private val debugRunnableLabelMap: MutableMap<Runnable, String> = HashMap()
+
+ // HeadsUpEntry we did not show at all because they are not the top priority hun in their batch
+ // For debugging only
+ @VisibleForTesting var debugDropSet: MutableSet<HeadsUpEntry> = HashSet()
+
+ /**
+ * Run or delay Runnable for given HeadsUpEntry
+ */
+ fun update(entry: HeadsUpEntry, runnable: Runnable, label: String) {
+ if (!NotificationThrottleHun.isEnabled) {
+ runnable.run()
+ return
+ }
+ val fn = "[$label] => AvalancheController.update ${getKey(entry)}"
+
+ if (debug) {
+ debugRunnableLabelMap[runnable] = label
+ }
+
+ if (isShowing(entry)) {
+ log {"$fn => [update showing]" }
+ runnable.run()
+ } else if (entry in nextMap) {
+ log { "$fn => [update next]" }
+ nextMap[entry]?.add(runnable)
+ } else if (headsUpEntryShowing == null) {
+ log { "$fn => [showNow]" }
+ showNow(entry, arrayListOf(runnable))
+ } else {
+ // Clean up invalid state when entry is in list but not map and vice versa
+ if (entry in nextMap) nextMap.remove(entry)
+ if (entry in nextList) nextList.remove(entry)
+
+ addToNext(entry, runnable)
+
+ // Shorten headsUpEntryShowing display time
+ val nextIndex = nextList.indexOf(entry)
+ val isOnlyNextEntry = nextIndex == 0 && nextList.size == 1
+ if (isOnlyNextEntry) {
+ // HeadsUpEntry.updateEntry recursively calls AvalancheController#update
+ // and goes to the isShowing case above
+ headsUpEntryShowing!!.updateEntry(false, "avalanche duration update")
+ }
+ }
+ logState("after $fn")
+ }
+
+ @VisibleForTesting
+ fun addToNext(entry: HeadsUpEntry, runnable: Runnable) {
+ nextMap[entry] = arrayListOf(runnable)
+ nextList.add(entry)
+ }
+
+ /**
+ * Run or ignore Runnable for given HeadsUpEntry. If entry was never shown, ignore and delete
+ * all Runnables associated with that entry.
+ */
+ fun delete(entry: HeadsUpEntry, runnable: Runnable, label: String) {
+ if (!NotificationThrottleHun.isEnabled) {
+ runnable.run()
+ return
+ }
+ val fn = "[$label] => AvalancheController.delete " + getKey(entry)
+
+ if (entry in nextMap) {
+ log { "$fn => [remove from next]" }
+ if (entry in nextMap) nextMap.remove(entry)
+ if (entry in nextList) nextList.remove(entry)
+ } else if (entry in debugDropSet) {
+ log { "$fn => [remove from dropset]" }
+ debugDropSet.remove(entry)
+ } else if (isShowing(entry)) {
+ log { "$fn => [remove showing ${getKey(entry)}]" }
+ runnable.run()
+ showNext()
+ } else {
+ log { "$fn => [removing untracked ${getKey(entry)}]" }
+ }
+ logState("after $fn")
+ }
+
+ /**
+ * Returns true if given HeadsUpEntry is the last one tracked by AvalancheController. Used by
+ * BaseHeadsUpManager.HeadsUpEntry.calculateFinishTime to shorten display duration during active
+ * avalanche.
+ */
+ fun shortenDuration(entry: HeadsUpEntry): Boolean {
+ if (!NotificationThrottleHun.isEnabled) {
+ // Use default display duration, like we always did before AvalancheController existed
+ return false
+ }
+ val showingList: MutableList<HeadsUpEntry> = mutableListOf()
+ headsUpEntryShowing?.let { showingList.add(it) }
+ val allEntryList = showingList + nextList
+
+ // Shorten duration if not last entry
+ return allEntryList.indexOf(entry) != allEntryList.size - 1
+ }
+
+ /**
+ * Return true if entry is waiting to show.
+ */
+ fun isWaiting(key: String): Boolean {
+ if (!NotificationThrottleHun.isEnabled) {
+ return false
+ }
+ for (entry in nextMap.keys) {
+ if (entry.mEntry?.key.equals(key)) {
+ return true
+ }
+ }
+ return false
+ }
+
+ /**
+ * Return list of keys for huns waiting
+ */
+ fun getWaitingKeys(): MutableList<String> {
+ if (!NotificationThrottleHun.isEnabled) {
+ return mutableListOf()
+ }
+ val keyList = mutableListOf<String>()
+ for (entry in nextMap.keys) {
+ entry.mEntry?.let { keyList.add(entry.mEntry!!.key) }
+ }
+ return keyList
+ }
+
+ private fun isShowing(entry: HeadsUpEntry): Boolean {
+ return headsUpEntryShowing != null && entry.mEntry?.key == headsUpEntryShowing?.mEntry?.key
+ }
+
+ private fun showNow(entry: HeadsUpEntry, runnableList: MutableList<Runnable>) {
+ log { "show " + getKey(entry) + " backlog size: " + runnableList.size }
+
+ headsUpEntryShowing = entry
+
+ runnableList.forEach {
+ if (it in debugRunnableLabelMap) {
+ log { "run runnable from: ${debugRunnableLabelMap[it]}" }
+ }
+ it.run()
+ }
+ }
+
+ private fun showNext() {
+ log { "showNext" }
+ headsUpEntryShowing = null
+
+ if (nextList.isEmpty()) {
+ log { "no more to show!" }
+ return
+ }
+
+ // Only show first (top priority) entry in next batch
+ nextList.sort()
+ headsUpEntryShowing = nextList[0]
+ headsUpEntryShowingRunnableList = nextMap[headsUpEntryShowing]!!
+
+ // Remove runnable labels for dropped huns
+ val listToDrop = nextList.subList(1, nextList.size)
+ if (debug) {
+ // Clear runnable labels
+ for (e in listToDrop) {
+ val runnableList = nextMap[e]!!
+ for (r in runnableList) {
+ debugRunnableLabelMap.remove(r)
+ }
+ }
+ debugDropSet.addAll(listToDrop)
+ }
+
+ clearNext()
+ showNow(headsUpEntryShowing!!, headsUpEntryShowingRunnableList)
+ }
+
+ fun clearNext() {
+ nextList.clear()
+ nextMap.clear()
+ }
+
+ // Methods below are for logging only ==========================================================
+
+ private inline fun log(s: () -> String) {
+ if (debug) {
+ Log.d(tag, s())
+ }
+ }
+
+ // TODO(b/315362456) expose as dumpable for bugreports
+ private fun logState(reason: String) {
+ log { "state $reason" }
+ log { "showing: " + getKey(headsUpEntryShowing) }
+ log { "next list: $nextListStr map: $nextMapStr" }
+ log { "drop: $dropSetStr" }
+ }
+
+ private val dropSetStr: String
+ get() {
+ val queue = ArrayList<String>()
+ for (entry in debugDropSet) {
+ queue.add(getKey(entry))
+ }
+ return java.lang.String.join(" ", queue)
+ }
+
+ private val nextListStr: String
+ get() {
+ val queue = ArrayList<String>()
+ for (entry in nextList) {
+ queue.add(getKey(entry))
+ }
+ return java.lang.String.join(" ", queue)
+ }
+
+ private val nextMapStr: String
+ get() {
+ val queue = ArrayList<String>()
+ for (entry in nextMap.keys) {
+ queue.add(getKey(entry))
+ }
+ return java.lang.String.join(" ", queue)
+ }
+
+ fun getKey(entry: HeadsUpEntry?): String {
+ if (entry == null) {
+ return "null"
+ }
+ if (entry.mEntry == null) {
+ return entry.toString()
+ }
+ return entry.mEntry!!.key
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index 530e49c..05cc73e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -45,6 +45,8 @@
import com.android.systemui.util.time.SystemClock;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
import java.util.stream.Stream;
/**
@@ -68,6 +70,7 @@
private final AccessibilityManagerWrapper mAccessibilityMgr;
private final UiEventLogger mUiEventLogger;
+ private final AvalancheController mAvalancheController;
protected final SystemClock mSystemClock;
protected final ArrayMap<String, HeadsUpEntry> mHeadsUpEntryMap = new ArrayMap<>();
@@ -100,13 +103,15 @@
SystemClock systemClock,
@Main DelayableExecutor executor,
AccessibilityManagerWrapper accessibilityManagerWrapper,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ AvalancheController avalancheController) {
mLogger = logger;
mExecutor = executor;
mSystemClock = systemClock;
mContext = context;
mAccessibilityMgr = accessibilityManagerWrapper;
mUiEventLogger = uiEventLogger;
+ mAvalancheController = avalancheController;
Resources resources = context.getResources();
mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
mStickyForSomeTimeAutoDismissTime = resources.getInteger(
@@ -157,18 +162,26 @@
*/
@Override
public void showNotification(@NonNull NotificationEntry entry) {
- mLogger.logShowNotification(entry);
-
- // Add new entry and begin managing it
HeadsUpEntry headsUpEntry = createHeadsUpEntry();
- headsUpEntry.setEntry(entry);
- mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry);
- onEntryAdded(headsUpEntry);
- entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- entry.setIsHeadsUpEntry(true);
- updateNotification(entry.getKey(), true /* shouldHeadsUpAgain */);
- entry.setInterruption();
+ // Attach NotificationEntry for AvalancheController to log key and
+ // record mPostTime for AvalancheController sorting
+ headsUpEntry.setEntry(entry);
+
+ Runnable runnable = () -> {
+ // TODO(b/315362456) log outside runnable too
+ mLogger.logShowNotification(entry);
+
+ // Add new entry and begin managing it
+ mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry);
+ onEntryAdded(headsUpEntry);
+ entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ entry.setIsHeadsUpEntry(true);
+
+ updateNotificationInternal(entry.getKey(), true /* shouldHeadsUpAgain */);
+ entry.setInterruption();
+ };
+ mAvalancheController.update(headsUpEntry, runnable, "showNotification");
}
/**
@@ -181,6 +194,11 @@
@Override
public boolean removeNotification(@NonNull String key, boolean releaseImmediately) {
mLogger.logRemoveNotification(key, releaseImmediately);
+
+ if (mAvalancheController.isWaiting(key)) {
+ removeEntry(key);
+ return true;
+ }
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
if (headsUpEntry == null) {
return true;
@@ -203,6 +221,14 @@
*/
public void updateNotification(@NonNull String key, boolean shouldHeadsUpAgain) {
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+ Runnable runnable = () -> {
+ updateNotificationInternal(key, shouldHeadsUpAgain);
+ };
+ mAvalancheController.update(headsUpEntry, runnable, "updateNotification");
+ }
+
+ private void updateNotificationInternal(@NonNull String key, boolean shouldHeadsUpAgain) {
+ HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
mLogger.logUpdateNotification(key, shouldHeadsUpAgain, headsUpEntry != null);
if (headsUpEntry == null) {
// the entry was released before this update (i.e by a listener) This can happen
@@ -231,12 +257,16 @@
for (String key : keysToRemove) {
removeEntry(key);
}
+ for (String key : mAvalancheController.getWaitingKeys()) {
+ removeEntry(key);
+ }
}
/**
* Returns the entry if it is managed by this manager.
* @param key key of notification
* @return the entry
+ * TODO(b/315362456) See if caller needs to check AvalancheController waiting entries
*/
@Nullable
public NotificationEntry getEntry(@NonNull String key) {
@@ -251,6 +281,7 @@
@NonNull
@Override
public Stream<NotificationEntry> getAllEntries() {
+ // TODO(b/315362456) See if callers need to check AvalancheController
return mHeadsUpEntryMap.values().stream().map(headsUpEntry -> headsUpEntry.mEntry);
}
@@ -267,7 +298,7 @@
* @return true if the notification is managed by this manager
*/
public boolean isHeadsUpEntry(@NonNull String key) {
- return mHeadsUpEntryMap.containsKey(key);
+ return mHeadsUpEntryMap.containsKey(key) || mAvalancheController.isWaiting(key);
}
/**
@@ -331,7 +362,7 @@
* Manager-specific logic that should occur when an entry is added.
* @param headsUpEntry entry added
*/
- protected void onEntryAdded(HeadsUpEntry headsUpEntry) {
+ void onEntryAdded(HeadsUpEntry headsUpEntry) {
NotificationEntry entry = headsUpEntry.mEntry;
entry.setHeadsUp(true);
@@ -349,20 +380,24 @@
*/
protected final void removeEntry(@NonNull String key) {
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
- if (headsUpEntry == null) {
- return;
- }
- NotificationEntry entry = headsUpEntry.mEntry;
- // If the notification is animating, we will remove it at the end of the animation.
- if (entry != null && entry.isExpandAnimationRunning()) {
- return;
- }
- entry.demoteStickyHun();
- mHeadsUpEntryMap.remove(key);
- onEntryRemoved(headsUpEntry);
- entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- headsUpEntry.reset();
+ Runnable runnable = () -> {
+ if (headsUpEntry == null) {
+ return;
+ }
+ NotificationEntry entry = headsUpEntry.mEntry;
+
+ // If the notification is animating, we will remove it at the end of the animation.
+ if (entry != null && entry.isExpandAnimationRunning()) {
+ return;
+ }
+ entry.demoteStickyHun();
+ mHeadsUpEntryMap.remove(key);
+ onEntryRemoved(headsUpEntry);
+ entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ headsUpEntry.reset();
+ };
+ mAvalancheController.delete(headsUpEntry, runnable, "removeEntry");
}
/**
@@ -380,7 +415,7 @@
}
}
- protected void updatePinnedMode() {
+ private void updatePinnedMode() {
boolean hasPinnedNotification = hasPinnedNotificationInternal();
if (hasPinnedNotification == mHasPinnedNotification) {
return;
@@ -416,7 +451,9 @@
* Snoozes all current Heads Up Notifications.
*/
public void snooze() {
- for (String key : mHeadsUpEntryMap.keySet()) {
+ List<String> keySet = new ArrayList<>(mHeadsUpEntryMap.keySet());
+ keySet.addAll(mAvalancheController.getWaitingKeys());
+ for (String key : keySet) {
HeadsUpEntry entry = getHeadsUpEntry(key);
String packageName = entry.mEntry.getSbn().getPackageName();
String snoozeKey = snoozeKey(packageName, mUser);
@@ -432,6 +469,7 @@
@Nullable
protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) {
+ // TODO(b/315362456) See if callers need to check AvalancheController
return (HeadsUpEntry) mHeadsUpEntryMap.get(key);
}
@@ -515,18 +553,22 @@
*/
public void unpinAll(boolean userUnPinned) {
for (String key : mHeadsUpEntryMap.keySet()) {
- HeadsUpEntry entry = getHeadsUpEntry(key);
- setEntryPinned(entry, false /* isPinned */);
- // maybe it got un sticky
- entry.updateEntry(false /* updatePostTime */, "unpinAll");
+ HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
- // when the user unpinned all of HUNs by moving one HUN, all of HUNs should not stay
- // on the screen.
- if (userUnPinned && entry.mEntry != null) {
- if (entry.mEntry.mustStayOnScreen()) {
- entry.mEntry.setHeadsUpIsVisible();
+ Runnable runnable = () -> {
+ setEntryPinned(headsUpEntry, false /* isPinned */);
+ // maybe it got un sticky
+ headsUpEntry.updateEntry(false /* updatePostTime */, "unpinAll");
+
+ // when the user unpinned all of HUNs by moving one HUN, all of HUNs should not stay
+ // on the screen.
+ if (userUnPinned && headsUpEntry.mEntry != null) {
+ if (headsUpEntry.mEntry.mustStayOnScreen()) {
+ headsUpEntry.mEntry.setHeadsUpIsVisible();
+ }
}
- }
+ };
+ mAvalancheController.delete(headsUpEntry, runnable, "unpinAll");
}
}
@@ -606,6 +648,7 @@
*/
@Override
public boolean isSticky(String key) {
+ // TODO(b/315362456) See if callers need to check AvalancheController
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
if (headsUpEntry != null) {
return headsUpEntry.isSticky();
@@ -633,9 +676,10 @@
/**
* This represents a notification and how long it is in a heads up mode. It also manages its
- * lifecycle automatically when created.
+ * lifecycle automatically when created. This class is public because it is exposed by methods
+ * of AvalancheController that take it as param.
*/
- protected class HeadsUpEntry implements Comparable<HeadsUpEntry> {
+ public class HeadsUpEntry implements Comparable<HeadsUpEntry> {
public boolean mRemoteInputActive;
public boolean mUserActionMayIndirectlyRemove;
@@ -672,27 +716,41 @@
}
/**
+ * An interface that returns the amount of time left this HUN should show.
+ */
+ interface FinishTimeUpdater {
+ long updateAndGetTimeRemaining();
+ }
+
+ /**
* Updates an entry's removal time.
* @param updatePostTime whether or not to refresh the post time
*/
public void updateEntry(boolean updatePostTime, @Nullable String reason) {
- mLogger.logUpdateEntry(mEntry, updatePostTime, reason);
+ Runnable runnable = () -> {
+ mLogger.logUpdateEntry(mEntry, updatePostTime, reason);
- final long now = mSystemClock.elapsedRealtime();
- mEarliestRemovalTime = now + mMinimumDisplayTime;
+ final long now = mSystemClock.elapsedRealtime();
+ mEarliestRemovalTime = now + mMinimumDisplayTime;
- if (updatePostTime) {
- mPostTime = Math.max(mPostTime, now);
- }
+ if (updatePostTime) {
+ mPostTime = Math.max(mPostTime, now);
+ }
+ };
+ mAvalancheController.update(this, runnable, "updateEntry (updatePostTime)");
if (isSticky()) {
- removeAutoRemovalCallbacks("updateEntry (sticky)");
+ cancelAutoRemovalCallbacks("updateEntry (sticky)");
return;
}
- final long finishTime = calculateFinishTime();
- final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
- scheduleAutoRemovalCallback(timeLeft, "updateEntry (not sticky)");
+ FinishTimeUpdater finishTimeCalculator = () -> {
+ final long finishTime = calculateFinishTime();
+ final long now = mSystemClock.elapsedRealtime();
+ final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
+ return timeLeft;
+ };
+ scheduleAutoRemovalCallback(finishTimeCalculator, "updateEntry (not sticky)");
}
/**
@@ -758,12 +816,31 @@
}
}
+ @Override
+ public int hashCode() {
+ if (mEntry == null) return super.hashCode();
+ int result = mEntry.getKey().hashCode();
+ result = 31 * result;
+ return result;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || !(o instanceof HeadsUpEntry)) return false;
+ HeadsUpEntry otherHeadsUpEntry = (HeadsUpEntry) o;
+ if (mEntry != null && otherHeadsUpEntry.mEntry != null) {
+ return mEntry.getKey().equals(otherHeadsUpEntry.mEntry.getKey());
+ }
+ return false;
+ }
+
public void setExpanded(boolean expanded) {
this.mExpanded = expanded;
}
public void reset() {
- removeAutoRemovalCallbacks("reset()");
+ cancelAutoRemovalCallbacks("reset()");
mEntry = null;
mRemoveRunnable = null;
mExpanded = false;
@@ -773,37 +850,48 @@
/**
* Clear any pending removal runnables.
*/
- public void removeAutoRemovalCallbacks(@Nullable String reason) {
- final boolean removed = removeAutoRemovalCallbackInternal();
+ public void cancelAutoRemovalCallbacks(@Nullable String reason) {
+ Runnable runnable = () -> {
+ final boolean removed = cancelAutoRemovalCallbackInternal();
- if (removed) {
- mLogger.logAutoRemoveCanceled(mEntry, reason);
- }
+ if (removed) {
+ mLogger.logAutoRemoveCanceled(mEntry, reason);
+ }
+ };
+ mAvalancheController.update(this, runnable,
+ reason + " removeAutoRemovalCallbacks");
}
- public void scheduleAutoRemovalCallback(long delayMillis, @NonNull String reason) {
- if (mRemoveRunnable == null) {
- Log.wtf(TAG, "scheduleAutoRemovalCallback with no callback set");
- return;
- }
+ public void scheduleAutoRemovalCallback(FinishTimeUpdater finishTimeCalculator,
+ @NonNull String reason) {
- final boolean removed = removeAutoRemovalCallbackInternal();
+ Runnable runnable = () -> {
+ long delayMs = finishTimeCalculator.updateAndGetTimeRemaining();
- if (removed) {
- mLogger.logAutoRemoveRescheduled(mEntry, delayMillis, reason);
- } else {
- mLogger.logAutoRemoveScheduled(mEntry, delayMillis, reason);
- }
+ if (mRemoveRunnable == null) {
+ Log.wtf(TAG, "scheduleAutoRemovalCallback with no callback set");
+ return;
+ }
- mCancelRemoveRunnable = mExecutor.executeDelayed(mRemoveRunnable,
- delayMillis);
+ final boolean deletedExistingRemovalRunnable = cancelAutoRemovalCallbackInternal();
+ mCancelRemoveRunnable = mExecutor.executeDelayed(mRemoveRunnable,
+ delayMs);
+
+ if (deletedExistingRemovalRunnable) {
+ mLogger.logAutoRemoveRescheduled(mEntry, delayMs, reason);
+ } else {
+ mLogger.logAutoRemoveScheduled(mEntry, delayMs, reason);
+ }
+ };
+ mAvalancheController.update(this, runnable,
+ reason + " scheduleAutoRemovalCallback");
}
- public boolean removeAutoRemovalCallbackInternal() {
+ public boolean cancelAutoRemovalCallbackInternal() {
final boolean scheduled = (mCancelRemoveRunnable != null);
if (scheduled) {
- mCancelRemoveRunnable.run();
+ mCancelRemoveRunnable.run(); // Delete removal runnable from Executor queue
mCancelRemoveRunnable = null;
}
@@ -815,8 +903,12 @@
*/
public void removeAsSoonAsPossible() {
if (mRemoveRunnable != null) {
- final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime();
- scheduleAutoRemovalCallback(timeLeft, "removeAsSoonAsPossible");
+
+ FinishTimeUpdater finishTimeCalculator = () -> {
+ final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime();
+ return timeLeft;
+ };
+ scheduleAutoRemovalCallback(finishTimeCalculator, "removeAsSoonAsPossible");
}
}
@@ -834,9 +926,15 @@
* {@link SystemClock#elapsedRealtime()}
*/
protected long calculateFinishTime() {
- final long duration = getRecommendedHeadsUpTimeoutMs(
- isStickyForSomeTime() ? mStickyForSomeTimeAutoDismissTime : mAutoDismissTime);
-
+ int requestedTimeOutMs;
+ if (isStickyForSomeTime()) {
+ requestedTimeOutMs = mStickyForSomeTimeAutoDismissTime;
+ } else if (mAvalancheController.shortenDuration(this)) {
+ requestedTimeOutMs = 1000;
+ } else {
+ requestedTimeOutMs = mAutoDismissTime;
+ }
+ final long duration = getRecommendedHeadsUpTimeoutMs(requestedTimeOutMs);
return mPostTime + duration;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 5bced93..9633cb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -135,7 +135,6 @@
@Nullable
private RevealParams mRevealParams;
private Rect mContentBackgroundBounds;
- private boolean mIsFocusAnimationFlagActive;
private boolean mIsAnimatingAppearance = false;
// TODO(b/193539698): move these to a Controller
@@ -432,7 +431,7 @@
// case to prevent flicker.
if (!mRemoved) {
ViewGroup parent = (ViewGroup) getParent();
- if (animate && parent != null && mIsFocusAnimationFlagActive) {
+ if (animate && parent != null) {
ViewGroup grandParent = (ViewGroup) parent.getParent();
View actionsContainer = getActionsContainerLayout();
@@ -497,8 +496,7 @@
}
private void setTopMargin(int topMargin) {
- if (!(getLayoutParams() instanceof FrameLayout.LayoutParams)) return;
- final FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
+ if (!(getLayoutParams() instanceof FrameLayout.LayoutParams layoutParams)) return;
layoutParams.topMargin = topMargin;
setLayoutParams(layoutParams);
}
@@ -608,24 +606,10 @@
}
/**
- * Sets whether the feature flag for the revised inline reply animation is active or not.
- * @param active
- */
- public void setIsFocusAnimationFlagActive(boolean active) {
- mIsFocusAnimationFlagActive = active;
- }
-
- /**
* Focuses the RemoteInputView and animates its appearance
*/
public void focusAnimated() {
- if (!mIsFocusAnimationFlagActive && getVisibility() != VISIBLE
- && mRevealParams != null) {
- android.animation.Animator animator = mRevealParams.createCircularRevealAnimator(this);
- animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- animator.start();
- } else if (mIsFocusAnimationFlagActive && getVisibility() != VISIBLE) {
+ if (getVisibility() != VISIBLE) {
mIsAnimatingAppearance = true;
setAlpha(0f);
Animator focusAnimator = getFocusAnimator(getActionsContainerLayout());
@@ -680,37 +664,19 @@
}
private void reset() {
- if (mIsFocusAnimationFlagActive) {
- mProgressBar.setVisibility(INVISIBLE);
- mResetting = true;
- mSending = false;
- mController.removeSpinning(mEntry.getKey(), mToken);
- onDefocus(true /* animate */, false /* logClose */, () -> {
- mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
- mEditText.getText().clear();
- mEditText.setEnabled(isAggregatedVisible());
- mSendButton.setVisibility(VISIBLE);
- updateSendButton();
- setAttachment(null);
- mResetting = false;
- });
- return;
- }
-
+ mProgressBar.setVisibility(INVISIBLE);
mResetting = true;
mSending = false;
- mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
-
- mEditText.getText().clear();
- mEditText.setEnabled(isAggregatedVisible());
- mSendButton.setVisibility(VISIBLE);
- mProgressBar.setVisibility(INVISIBLE);
mController.removeSpinning(mEntry.getKey(), mToken);
- updateSendButton();
- onDefocus(false /* animate */, false /* logClose */, null /* doAfterDefocus */);
- setAttachment(null);
-
- mResetting = false;
+ onDefocus(true /* animate */, false /* logClose */, () -> {
+ mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
+ mEditText.getText().clear();
+ mEditText.setEnabled(isAggregatedVisible());
+ mSendButton.setVisibility(VISIBLE);
+ updateSendButton();
+ setAttachment(null);
+ mResetting = false;
+ });
}
@Override
@@ -854,7 +820,7 @@
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
- if (mIsFocusAnimationFlagActive) setPivotY(getMeasuredHeight());
+ setPivotY(getMeasuredHeight());
if (mContentBackgroundBounds != null) {
mContentBackground.setBounds(mContentBackgroundBounds);
}
@@ -1015,9 +981,9 @@
private RemoteInputView mRemoteInputView;
boolean mShowImeOnInputConnection;
- private LightBarController mLightBarController;
+ private final LightBarController mLightBarController;
private InputMethodManager mInputMethodManager;
- private ArraySet<String> mSupportedMimes = new ArraySet<>();
+ private final ArraySet<String> mSupportedMimes = new ArraySet<>();
UserHandle mUser;
public RemoteEditText(Context context, AttributeSet attrs) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
index 6c0d433..bfee9ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
@@ -32,7 +32,6 @@
import com.android.internal.logging.UiEventLogger
import com.android.systemui.res.R
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags.NOTIFICATION_INLINE_REPLY_ANIMATION
import com.android.systemui.statusbar.NotificationRemoteInputManager
import com.android.systemui.statusbar.RemoteInputController
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -64,8 +63,6 @@
var revealParams: RevealParams?
- val isFocusAnimationFlagActive: Boolean
-
/**
* Sets the smart reply that should be inserted in the remote input, or `null` if the user is
* not editing a smart reply.
@@ -155,9 +152,6 @@
override val isActive: Boolean get() = view.isActive
- override val isFocusAnimationFlagActive: Boolean
- get() = mFlags.isEnabled(NOTIFICATION_INLINE_REPLY_ANIMATION)
-
override fun bind() {
if (isBound) return
isBound = true
@@ -168,7 +162,6 @@
view.setSupportedMimeTypes(it.allowedDataTypes)
}
view.setRevealParameters(revealParams)
- view.setIsFocusAnimationFlagActive(isFocusAnimationFlagActive)
view.addOnEditTextFocusChangedListener(onFocusChangeListener)
view.addOnSendRemoteInputListener(onSendRemoteInputListener)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ResourcesSplitShadeStateController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ResourcesSplitShadeStateController.kt
index 859e636..213324a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ResourcesSplitShadeStateController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ResourcesSplitShadeStateController.kt
@@ -26,6 +26,15 @@
* based solely on resources, no extra flag logic.
*/
class ResourcesSplitShadeStateController @Inject constructor() : SplitShadeStateController {
+
+ @Deprecated(
+ message = "This is deprecated, please use ShadeInteractor#isSplitShade instead",
+ replaceWith =
+ ReplaceWith(
+ "shadeInteractor.isSplitShade",
+ "com.android.systemui.shade.domain.interactor.ShadeInteractor",
+ ),
+ )
override fun shouldUseSplitNotificationShade(resources: Resources): Boolean {
return resources.getBoolean(R.bool.config_use_split_notification_shade)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateController.kt
index f64d4c6..d120a1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateController.kt
@@ -19,6 +19,15 @@
/** Source of truth for split shade state: should or should not use split shade. */
interface SplitShadeStateController {
+
/** Returns true if the device should use the split notification shade. */
+ @Deprecated(
+ message = "This is deprecated, please use ShadeInteractor#isSplitShade instead",
+ replaceWith =
+ ReplaceWith(
+ "shadeInteractor.isSplitShade",
+ "com.android.systemui.shade.domain.interactor.ShadeInteractor",
+ ),
+ )
fun shouldUseSplitNotificationShade(resources: Resources): Boolean
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateControllerImpl.kt
index 43905c5..5c5b17e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateControllerImpl.kt
@@ -16,10 +16,10 @@
package com.android.systemui.statusbar.policy
import android.content.res.Resources
-import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.res.R
import javax.inject.Inject
/**
@@ -29,10 +29,15 @@
@SysUISingleton
class SplitShadeStateControllerImpl @Inject constructor(private val featureFlags: FeatureFlags) :
SplitShadeStateController {
- /**
- * Returns true if the device should use the split notification shade. Based on orientation,
- * screen width, and flags.
- */
+
+ @Deprecated(
+ message = "This is deprecated, please use ShadeInteractor#isSplitShade instead",
+ replaceWith =
+ ReplaceWith(
+ "shadeInteractor.isSplitShade",
+ "com.android.systemui.shade.domain.interactor.ShadeInteractor",
+ ),
+ )
override fun shouldUseSplitNotificationShade(resources: Resources): Boolean {
return (resources.getBoolean(R.bool.config_use_split_notification_shade) ||
(featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE) &&
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
index 5c53ff9..ac1d280 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
@@ -16,6 +16,8 @@
package com.android.systemui.unfold
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.annotation.BinderThread
import android.content.Context
@@ -23,7 +25,6 @@
import android.os.SystemProperties
import android.util.Log
import android.view.animation.DecelerateInterpolator
-import androidx.core.animation.addListener
import com.android.internal.foldables.FoldLockSettingAvailabilityProvider
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DeviceStateRepository
@@ -36,17 +37,25 @@
import com.android.systemui.unfold.dagger.UnfoldBg
import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
import javax.inject.Inject
+import kotlin.coroutines.resume
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.android.asCoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeout
+@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
class FoldLightRevealOverlayAnimation
@Inject
constructor(
@@ -61,6 +70,9 @@
private val revealProgressValueAnimator: ValueAnimator =
ValueAnimator.ofFloat(ALPHA_OPAQUE, ALPHA_TRANSPARENT)
+ private val areAnimationEnabled: Flow<Boolean>
+ get() = animationStatusRepository.areAnimationsEnabled()
+
private lateinit var controller: FullscreenLightRevealAnimationController
@Volatile private var readyCallback: CompletableDeferred<Runnable>? = null
@@ -89,33 +101,31 @@
applicationScope.launch(bgHandler.asCoroutineDispatcher()) {
deviceStateRepository.state
- .map { it != DeviceStateRepository.DeviceState.FOLDED }
+ .map { it == DeviceStateRepository.DeviceState.FOLDED }
.distinctUntilChanged()
- .filter { isUnfolded -> isUnfolded }
- .collect { controller.ensureOverlayRemoved() }
- }
-
- applicationScope.launch(bgHandler.asCoroutineDispatcher()) {
- deviceStateRepository.state
- .filter {
- animationStatusRepository.areAnimationsEnabled().first() &&
- it == DeviceStateRepository.DeviceState.FOLDED
- }
- .collect {
- try {
- withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) {
- readyCallback = CompletableDeferred()
- val onReady = readyCallback?.await()
- readyCallback = null
- controller.addOverlay(ALPHA_OPAQUE, onReady)
- waitForScreenTurnedOn()
+ .flatMapLatest { isFolded ->
+ flow<Nothing> {
+ if (!areAnimationEnabled.first() || !isFolded) {
+ return@flow
+ }
+ withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) {
+ readyCallback = CompletableDeferred()
+ val onReady = readyCallback?.await()
+ readyCallback = null
+ controller.addOverlay(ALPHA_OPAQUE, onReady)
+ waitForScreenTurnedOn()
+ }
playFoldLightRevealOverlayAnimation()
}
- } catch (e: TimeoutCancellationException) {
- Log.e(TAG, "Fold light reveal animation timed out")
- ensureOverlayRemovedInternal()
- }
+ .catchTimeoutAndLog()
+ .onCompletion {
+ controller.ensureOverlayRemoved()
+ val onReady = readyCallback?.takeIf { it.isCompleted }?.getCompleted()
+ onReady?.run()
+ readyCallback = null
+ }
}
+ .collect {}
}
}
@@ -128,19 +138,34 @@
powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first()
}
- private fun ensureOverlayRemovedInternal() {
- revealProgressValueAnimator.cancel()
- controller.ensureOverlayRemoved()
- }
-
- private fun playFoldLightRevealOverlayAnimation() {
+ private suspend fun playFoldLightRevealOverlayAnimation() {
revealProgressValueAnimator.duration = ANIMATION_DURATION
revealProgressValueAnimator.interpolator = DecelerateInterpolator()
revealProgressValueAnimator.addUpdateListener { animation ->
controller.updateRevealAmount(animation.animatedFraction)
}
- revealProgressValueAnimator.addListener(onEnd = { controller.ensureOverlayRemoved() })
- revealProgressValueAnimator.start()
+ revealProgressValueAnimator.startAndAwaitCompletion()
+ }
+
+ private suspend fun ValueAnimator.startAndAwaitCompletion(): Unit =
+ suspendCancellableCoroutine { continuation ->
+ val listener =
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ continuation.resume(Unit)
+ removeListener(this)
+ }
+ }
+ addListener(listener)
+ continuation.invokeOnCancellation { removeListener(listener) }
+ start()
+ }
+
+ private fun <T> Flow<T>.catchTimeoutAndLog() = catch { exception ->
+ when (exception) {
+ is TimeoutCancellationException -> Log.e(TAG, "Fold light reveal animation timed out")
+ else -> throw exception
+ }
}
private companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
index a925e38..f755feb 100644
--- a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
@@ -27,6 +27,7 @@
import android.util.SparseBooleanArray;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.internal.telephony.TelephonyIntents;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -72,7 +73,7 @@
@Inject
public CarrierConfigTracker(
- CarrierConfigManager carrierConfigManager,
+ @Nullable CarrierConfigManager carrierConfigManager,
BroadcastDispatcher broadcastDispatcher) {
mCarrierConfigManager = carrierConfigManager;
IntentFilter filter = new IntentFilter();
@@ -95,6 +96,9 @@
final int subId = intent.getIntExtra(
CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ if (mCarrierConfigManager == null) {
+ return;
+ }
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
index cabe831..d10554f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
@@ -32,7 +32,7 @@
private const val LIMIT_BACKGROUND_DISPATCHER_THREADS = true
-/** Providers for various SystemIU specific coroutines-related constructs. */
+/** Providers for various SystemUI-specific coroutines-related constructs. */
@Module
class SysUICoroutinesModule {
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/ClientTrackingWakeLock.kt b/packages/SystemUI/src/com/android/systemui/util/wakelock/ClientTrackingWakeLock.kt
index db300eb..2157fafa 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/ClientTrackingWakeLock.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/ClientTrackingWakeLock.kt
@@ -18,6 +18,7 @@
import android.os.PowerManager
import android.util.Log
+import com.android.systemui.util.wakelock.WakeLock.Builder.NO_TIMEOUT
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicInteger
@@ -36,7 +37,11 @@
override fun acquire(why: String) {
val count = activeClients.computeIfAbsent(why) { _ -> AtomicInteger(0) }.incrementAndGet()
logger?.logAcquire(pmWakeLock, why, count)
- pmWakeLock.acquire(maxTimeout)
+ if (maxTimeout == NO_TIMEOUT) {
+ pmWakeLock.acquire()
+ } else {
+ pmWakeLock.acquire(maxTimeout)
+ }
}
override fun release(why: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
index 707751a..f763ee4 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
@@ -130,7 +130,11 @@
if (logger != null) {
logger.logAcquire(inner, why, count);
}
- inner.acquire(maxTimeout);
+ if (maxTimeout == Builder.NO_TIMEOUT) {
+ inner.acquire();
+ } else {
+ inner.acquire(maxTimeout);
+ }
}
/** @see PowerManager.WakeLock#release() */
@@ -169,6 +173,7 @@
* An injectable Builder that wraps {@link #createPartial(Context, String, long)}.
*/
class Builder {
+ public static final long NO_TIMEOUT = -1;
private final Context mContext;
private final WakeLockLogger mLogger;
private String mTag;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index deec215..c3274477 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -173,6 +173,9 @@
private static final String TYPE_DISMISS = "dismiss";
/** Volume dialog slider animation. */
private static final String TYPE_UPDATE = "update";
+ static final short PROGRESS_HAPTICS_DISABLED = 0;
+ static final short PROGRESS_HAPTICS_EAGER = 1;
+ static final short PROGRESS_HAPTICS_ANIMATED = 2;
/**
* TODO(b/290612381): remove lingering animations or tolerate them
@@ -2077,14 +2080,17 @@
if (row.anim == null) {
row.anim = ObjectAnimator.ofInt(row.slider, "progress", progress, newProgress);
row.anim.setInterpolator(new DecelerateInterpolator());
- row.anim.addListener(
- getJankListener(row.view, TYPE_UPDATE, UPDATE_ANIMATION_DURATION));
+ Animator.AnimatorListener listener =
+ getJankListener(row.view, TYPE_UPDATE, UPDATE_ANIMATION_DURATION);
+ if (listener != null) {
+ row.anim.addListener(listener);
+ }
} else {
row.anim.cancel();
row.anim.setIntValues(progress, newProgress);
// The animator can't keep up with the volume changes so haptics need to be
// triggered here. This happens when the volume keys are continuously pressed.
- row.deliverOnProgressChangedHaptics(false, newProgress);
+ row.deliverOnProgressChangedHaptics(false, newProgress, PROGRESS_HAPTICS_EAGER);
}
row.animTargetProgress = newProgress;
row.anim.setDuration(UPDATE_ANIMATION_DURATION);
@@ -2099,6 +2105,15 @@
}
}
+ @VisibleForTesting short progressHapticsForStream(int stream) {
+ for (VolumeRow row: mRows) {
+ if (row.stream == stream) {
+ return row.mProgressHapticsType;
+ }
+ }
+ return PROGRESS_HAPTICS_DISABLED;
+ }
+
private void recheckH(VolumeRow row) {
if (row == null) {
if (D.BUG) Log.d(TAG, "recheckH ALL");
@@ -2486,13 +2501,12 @@
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (mRow.ss == null) return;
- if (getActiveRow().equals(mRow)
- && mRow.slider.getVisibility() == VISIBLE
- && mRow.mHapticPlugin != null) {
+ if (getActiveRow().equals(mRow) && mRow.slider.getVisibility() == VISIBLE) {
if (fromUser || mRow.animTargetProgress == progress) {
- // Deliver user-generated slider changes immediately, or when the animation
+ // Deliver user-generated slider haptics immediately, or when the animation
// completes
- mRow.deliverOnProgressChangedHaptics(fromUser, progress);
+ mRow.deliverOnProgressChangedHaptics(
+ fromUser, progress, PROGRESS_HAPTICS_ANIMATED);
}
}
if (D.BUG) Log.d(TAG, AudioSystem.streamToString(mRow.stream)
@@ -2605,6 +2619,7 @@
private int animTargetProgress;
private int lastAudibleLevel = 1;
private SeekableSliderHapticPlugin mHapticPlugin;
+ private short mProgressHapticsType = PROGRESS_HAPTICS_DISABLED;
void setIcon(int iconRes, Resources.Theme theme) {
if (icon != null) {
@@ -2646,12 +2661,15 @@
slider.setOnTouchListener(null);
}
- void deliverOnProgressChangedHaptics(boolean fromUser, int progress) {
+ void deliverOnProgressChangedHaptics(boolean fromUser, int progress, short hapticsType) {
+ if (mHapticPlugin == null) return;
+
mHapticPlugin.onProgressChanged(slider, progress, fromUser);
if (!fromUser) {
// Consider a change from program as the volume key being continuously pressed
mHapticPlugin.onKeyDown();
}
+ mProgressHapticsType = hapticsType;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt
index 593b90a..4ba7cbb 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt
@@ -21,12 +21,10 @@
import com.android.settingslib.media.data.repository.SpatializerRepository
import com.android.settingslib.media.data.repository.SpatializerRepositoryImpl
import com.android.settingslib.media.domain.interactor.SpatializerInteractor
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import dagger.Module
import dagger.Provides
import kotlin.coroutines.CoroutineContext
-import kotlinx.coroutines.CoroutineScope
/** Spatializer module. */
@Module
@@ -42,9 +40,8 @@
@Provides
fun provdieSpatializerRepository(
spatializer: Spatializer,
- @Application scope: CoroutineScope,
@Background backgroundContext: CoroutineContext,
- ): SpatializerRepository = SpatializerRepositoryImpl(spatializer, scope, backgroundContext)
+ ): SpatializerRepository = SpatializerRepositoryImpl(spatializer, backgroundContext)
@Provides
fun provideSpatializerInetractor(repository: SpatializerRepository): SpatializerInteractor =
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt
index 8ab563a..6c47aec 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt
@@ -23,3 +23,6 @@
val icon: Icon,
val label: CharSequence,
)
+
+fun ToggleButtonViewModel.toButtonViewModel(): ButtonViewModel =
+ ButtonViewModel(icon = icon, label = label)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
index 71df8e5..1bceee9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
@@ -36,3 +36,7 @@
/** Current media state is unknown yet. */
data object Unknown : MediaDeviceSession
}
+
+/** Returns true when the audio is playing for the [MediaDeviceSession]. */
+fun MediaDeviceSession.isPlaying(): Boolean =
+ this is MediaDeviceSession.Active && playbackState?.isActive == true
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index 37661b5..d49cb1e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -24,6 +24,7 @@
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.isPlaying
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
import javax.inject.Inject
@@ -110,9 +111,6 @@
null,
)
- private fun MediaDeviceSession.isPlaying(): Boolean =
- this is MediaDeviceSession.Active && playbackState?.isActive == true
-
fun onBarClick(expandable: Expandable) {
actionsInteractor.onBarClick(mediaDeviceSession.value, expandable)
volumePanelViewModel.dismissPanel()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
index 9d801fc..9ef07fa 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
@@ -24,5 +24,6 @@
const val BOTTOM_BAR: VolumePanelComponentKey = "bottom_bar"
const val VOLUME_SLIDERS: VolumePanelComponentKey = "volume_sliders"
const val CAPTIONING: VolumePanelComponentKey = "captioning"
+ const val SPATIAL_AUDIO: VolumePanelComponentKey = "spatial_audio"
const val ANC: VolumePanelComponentKey = "anc"
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
index 4358611..6032bfe 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
@@ -71,10 +71,10 @@
combine(
currentAudioDeviceAttributes,
changes.onStart { emit(Unit) },
- spatializerInteractor.isHeadTrackingAvailable,
- ) { attributes, _, isHeadTrackingAvailable ->
+ ) { attributes, _,
+ ->
attributes ?: return@combine SpatialAudioAvailabilityModel.Unavailable
- if (isHeadTrackingAvailable) {
+ if (spatializerInteractor.isHeadTrackingAvailable(attributes)) {
return@combine SpatialAudioAvailabilityModel.HeadTracking
}
if (spatializerInteractor.isSpatialAudioAvailable(attributes)) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt
index 4e65f60..9735e5c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt
@@ -19,6 +19,16 @@
/** Models spatial audio and head tracking enabled/disabled state. */
interface SpatialAudioEnabledModel {
+ companion object {
+ /** All possible SpatialAudioEnabledModel implementations. */
+ val values =
+ listOf(
+ Disabled,
+ SpatialAudioEnabled,
+ HeadTrackingEnabled,
+ )
+ }
+
/** Spatial audio is disabled. */
data object Disabled : SpatialAudioEnabledModel
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt
new file mode 100644
index 0000000..9f9275b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.spatial.ui.viewmodel
+
+import com.android.systemui.common.shared.model.Color
+import com.android.systemui.volume.panel.component.button.ui.viewmodel.ToggleButtonViewModel
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
+
+data class SpatialAudioButtonViewModel(
+ val model: SpatialAudioEnabledModel,
+ val button: ToggleButtonViewModel,
+ val iconColor: Color,
+ val labelColor: Color,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
new file mode 100644
index 0000000..30715d1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.spatial.ui.viewmodel
+
+import android.content.Context
+import com.android.systemui.common.shared.model.Color
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
+import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
+import com.android.systemui.volume.panel.component.button.ui.viewmodel.ToggleButtonViewModel
+import com.android.systemui.volume.panel.component.button.ui.viewmodel.toButtonViewModel
+import com.android.systemui.volume.panel.component.spatial.domain.SpatialAudioAvailabilityCriteria
+import com.android.systemui.volume.panel.component.spatial.domain.interactor.SpatialAudioComponentInteractor
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+@VolumePanelScope
+class SpatialAudioViewModel
+@Inject
+constructor(
+ @Application private val context: Context,
+ @VolumePanelScope private val scope: CoroutineScope,
+ availabilityCriteria: SpatialAudioAvailabilityCriteria,
+ private val interactor: SpatialAudioComponentInteractor,
+) {
+
+ val spatialAudioButton: StateFlow<ButtonViewModel?> =
+ interactor.isEnabled
+ .map { it.toViewModel(true).toButtonViewModel() }
+ .stateIn(scope, SharingStarted.Eagerly, null)
+
+ val isAvailable: StateFlow<Boolean> =
+ availabilityCriteria.isAvailable().stateIn(scope, SharingStarted.Eagerly, true)
+
+ val spatialAudioButtonByEnabled: StateFlow<List<SpatialAudioButtonViewModel>> =
+ combine(interactor.isEnabled, interactor.isAvailable) { currentIsEnabled, isAvailable ->
+ SpatialAudioEnabledModel.values
+ .filter {
+ if (it is SpatialAudioEnabledModel.HeadTrackingEnabled) {
+ // Spatial audio control can be visible when there is spatial audio
+ // setting available but not the head tracking.
+ isAvailable is SpatialAudioAvailabilityModel.HeadTracking
+ } else {
+ true
+ }
+ }
+ .map { isEnabled ->
+ val isChecked = isEnabled == currentIsEnabled
+ val buttonViewModel: ToggleButtonViewModel =
+ isEnabled.toViewModel(isChecked)
+ SpatialAudioButtonViewModel(
+ button = buttonViewModel,
+ model = isEnabled,
+ iconColor =
+ Color.Attribute(
+ if (isChecked)
+ com.android.internal.R.attr.materialColorOnPrimaryContainer
+ else com.android.internal.R.attr.materialColorOnSurfaceVariant
+ ),
+ labelColor =
+ Color.Attribute(
+ if (isChecked)
+ com.android.internal.R.attr.materialColorOnSurface
+ else com.android.internal.R.attr.materialColorOutline
+ ),
+ )
+ }
+ }
+ .stateIn(scope, SharingStarted.Eagerly, emptyList())
+
+ fun setEnabled(model: SpatialAudioEnabledModel) {
+ scope.launch { interactor.setEnabled(model) }
+ }
+
+ private fun SpatialAudioEnabledModel.toViewModel(isChecked: Boolean): ToggleButtonViewModel {
+ if (this is SpatialAudioEnabledModel.HeadTrackingEnabled) {
+ return ToggleButtonViewModel(
+ isChecked = isChecked,
+ icon = Icon.Resource(R.drawable.ic_head_tracking, contentDescription = null),
+ label = context.getString(R.string.volume_panel_spatial_audio_tracking)
+ )
+ }
+
+ if (this is SpatialAudioEnabledModel.SpatialAudioEnabled) {
+ return ToggleButtonViewModel(
+ isChecked = isChecked,
+ icon = Icon.Resource(R.drawable.ic_spatial_audio, contentDescription = null),
+ label = context.getString(R.string.volume_panel_spatial_audio_fixed)
+ )
+ }
+
+ if (this is SpatialAudioEnabledModel.Disabled) {
+ return ToggleButtonViewModel(
+ isChecked = isChecked,
+ icon = Icon.Resource(R.drawable.ic_spatial_audio_off, contentDescription = null),
+ label = context.getString(R.string.volume_panel_spatial_audio_off)
+ )
+ }
+
+ error("Unsupported model: $this")
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index faf7434..532e517 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -21,6 +21,7 @@
import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.res.R
import com.android.systemui.volume.panel.component.volume.domain.interactor.VolumeSliderInteractor
@@ -54,14 +55,6 @@
AudioStream(AudioManager.STREAM_NOTIFICATION) to R.drawable.ic_volume_ringer,
AudioStream(AudioManager.STREAM_ALARM) to R.drawable.ic_volume_alarm,
)
- private val mutedIconsByStream =
- mapOf(
- AudioStream(AudioManager.STREAM_MUSIC) to R.drawable.ic_volume_off,
- AudioStream(AudioManager.STREAM_VOICE_CALL) to R.drawable.ic_volume_off,
- AudioStream(AudioManager.STREAM_RING) to R.drawable.ic_volume_off,
- AudioStream(AudioManager.STREAM_NOTIFICATION) to R.drawable.ic_volume_off,
- AudioStream(AudioManager.STREAM_ALARM) to R.drawable.ic_volume_off,
- )
private val labelsByStream =
mapOf(
AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_music,
@@ -74,6 +67,8 @@
mapOf(
AudioStream(AudioManager.STREAM_NOTIFICATION) to
R.string.stream_notification_unavailable,
+ AudioStream(AudioManager.STREAM_ALARM) to R.string.stream_alarm_unavailable,
+ AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_media_unavailable,
)
private var value = 0f
@@ -81,8 +76,9 @@
combine(
audioVolumeInteractor.getAudioStream(audioStream),
audioVolumeInteractor.canChangeVolume(audioStream),
- ) { model, isEnabled ->
- model.toState(value, isEnabled)
+ audioVolumeInteractor.ringerMode,
+ ) { model, isEnabled, ringerMode ->
+ model.toState(value, isEnabled, ringerMode)
}
.stateIn(coroutineScope, SharingStarted.Eagerly, EmptyState)
@@ -100,7 +96,11 @@
}
}
- private fun AudioStreamModel.toState(value: Float, isEnabled: Boolean): State {
+ private fun AudioStreamModel.toState(
+ value: Float,
+ isEnabled: Boolean,
+ ringerMode: RingerMode,
+ ): State {
return State(
value =
volumeSliderInteractor.processVolumeToValue(
@@ -110,7 +110,7 @@
isMuted,
),
valueRange = volumeSliderInteractor.displayValueRange,
- icon = getIcon(this),
+ icon = getIcon(ringerMode),
label = labelsByStream[audioStream]?.let(context::getString)
?: error("No label for the stream: $audioStream"),
disabledMessage = disabledTextByStream[audioStream]?.let(context::getString),
@@ -119,14 +119,31 @@
)
}
- private fun getIcon(model: AudioStreamModel): Icon {
- val isMutedOrNoVolume = model.isMuted || model.volume == model.minVolume
+ private fun AudioStreamModel.getIcon(ringerMode: RingerMode): Icon {
+ val isMutedOrNoVolume = isMuted || volume == minVolume
val iconRes =
if (isMutedOrNoVolume) {
- mutedIconsByStream
+ when (audioStream.value) {
+ AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_off
+ AudioManager.STREAM_VOICE_CALL -> R.drawable.ic_volume_off
+ AudioManager.STREAM_RING ->
+ if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
+ R.drawable.ic_volume_ringer_vibrate
+ } else {
+ R.drawable.ic_volume_off
+ }
+ AudioManager.STREAM_NOTIFICATION ->
+ if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
+ R.drawable.ic_volume_ringer_vibrate
+ } else {
+ R.drawable.ic_volume_off
+ }
+ AudioManager.STREAM_ALARM -> R.drawable.ic_volume_off
+ else -> null
+ }
} else {
- iconsByStream
- }[audioStream]
+ iconsByStream[audioStream]
+ }
?: error("No icon for the stream: $audioStream")
return Icon.Resource(iconRes, null)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
index 2824323..aaee24b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
@@ -18,6 +18,8 @@
import android.media.AudioManager
import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.isPlaying
import com.android.systemui.volume.panel.component.volume.domain.interactor.CastVolumeInteractor
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.AudioStreamSliderViewModel
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.CastVolumeSliderViewModel
@@ -28,12 +30,17 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
+import kotlinx.coroutines.launch
/**
* Controls the behaviour of the whole audio
@@ -46,6 +53,7 @@
constructor(
@VolumePanelScope private val scope: CoroutineScope,
castVolumeInteractor: CastVolumeInteractor,
+ mediaOutputInteractor: MediaOutputInteractor,
private val streamSliderViewModelFactory: AudioStreamSliderViewModel.Factory,
private val castVolumeSliderViewModelFactory: CastVolumeSliderViewModel.Factory,
) {
@@ -90,4 +98,17 @@
remoteSessionsViewModels + streamViewModels
}
.stateIn(scope, SharingStarted.Eagerly, emptyList())
+
+ private val mutableIsExpanded = MutableSharedFlow<Boolean>()
+
+ val isExpanded: StateFlow<Boolean> =
+ merge(
+ mutableIsExpanded.onStart { emit(false) },
+ mediaOutputInteractor.mediaDeviceSession.map { !it.isPlaying() },
+ )
+ .stateIn(scope, SharingStarted.Eagerly, false)
+
+ fun onExpandedChanged(isExpanded: Boolean) {
+ scope.launch { mutableIsExpanded.emit(isExpanded) }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
index f31ee86..d868c33 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
@@ -20,6 +20,7 @@
import com.android.systemui.volume.panel.component.bottombar.BottomBarModule
import com.android.systemui.volume.panel.component.captioning.CaptioningModule
import com.android.systemui.volume.panel.component.mediaoutput.MediaOutputModule
+import com.android.systemui.volume.panel.component.spatialaudio.SpatialAudioModule
import com.android.systemui.volume.panel.component.volume.VolumeSlidersModule
import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
@@ -49,6 +50,7 @@
// Components modules
BottomBarModule::class,
AncModule::class,
+ SpatialAudioModule::class,
VolumeSlidersModule::class,
CaptioningModule::class,
MediaOutputModule::class,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
index 57ea997..999f4c1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
@@ -51,6 +51,7 @@
fun provideEnabledComponents(): Collection<VolumePanelComponentKey> {
return setOf(
VolumePanelComponents.ANC,
+ VolumePanelComponents.SPATIAL_AUDIO,
VolumePanelComponents.CAPTIONING,
VolumePanelComponents.VOLUME_SLIDERS,
VolumePanelComponents.MEDIA_OUTPUT,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
index ec4da06..8ba06e1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
@@ -49,6 +49,7 @@
fun provideFooterComponents(): Collection<VolumePanelComponentKey> {
return listOf(
VolumePanelComponents.ANC,
+ VolumePanelComponents.SPATIAL_AUDIO,
VolumePanelComponents.CAPTIONING,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
index 7fd9c8a..635191a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
@@ -46,12 +46,18 @@
!footerComponents.contains(it.key) &&
it.key != bottomBar
}
- val headerComponents = components.filter { headerComponents.contains(it.key) }
- val footerComponents = components.filter { footerComponents.contains(it.key) }
+ val headerComponents =
+ components
+ .filter { it.key in headerComponents }
+ .sortedBy { headerComponents.indexOf(it.key) }
+ val footerComponents =
+ components
+ .filter { it.key in footerComponents }
+ .sortedBy { footerComponents.indexOf(it.key) }
return ComponentsLayout(
- headerComponents = headerComponents.sortedBy { it.key },
+ headerComponents = headerComponents,
contentComponents = contentComponents.sortedBy { it.key },
- footerComponents = footerComponents.sortedBy { it.key },
+ footerComponents = footerComponents,
bottomBarComponent = components.find { it.key == bottomBar }
?: error(
"VolumePanelComponents.BOTTOM_BAR must be present in the default " +
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 324d723..7674fe9 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -361,12 +361,14 @@
public void enterDesktop(int displayId) {
desktopMode.enterDesktop(displayId);
}
- });
- mCommandQueue.addCallback(new CommandQueue.Callbacks() {
@Override
public void moveFocusedTaskToFullscreen(int displayId) {
desktopMode.moveFocusedTaskToFullscreen(displayId);
}
+ @Override
+ public void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop) {
+ desktopMode.moveFocusedTaskToStageSplit(displayId, leftOrTop);
+ }
});
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
index 13fb42c..90587d7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
@@ -16,8 +16,6 @@
package com.android.keyguard;
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -32,7 +30,6 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.power.data.repository.FakePowerRepository;
import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
import com.android.systemui.res.R;
@@ -62,7 +59,6 @@
@Mock protected KeyguardStatusViewController mControllerMock;
@Mock protected InteractionJankMonitor mInteractionJankMonitor;
@Mock protected ViewTreeObserver mViewTreeObserver;
- @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@Mock protected DumpManager mDumpManager;
protected FakeKeyguardRepository mFakeKeyguardRepository;
protected FakePowerRepository mFakePowerRepository;
@@ -93,7 +89,6 @@
mKeyguardLogger,
mInteractionJankMonitor,
deps.getKeyguardInteractor(),
- mKeyguardTransitionInteractor,
mDumpManager,
PowerInteractorFactory.create(
mFakePowerRepository
@@ -110,7 +105,6 @@
when(mKeyguardStatusView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
when(mKeyguardClockSwitchController.getView()).thenReturn(mKeyguardClockSwitch);
- when(mKeyguardTransitionInteractor.getGoneToAodTransition()).thenReturn(emptyFlow());
when(mKeyguardStatusView.findViewById(R.id.keyguard_status_area))
.thenReturn(mKeyguardStatusAreaView);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
index dd428f5..ccdcee5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
@@ -32,7 +32,6 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -57,7 +56,6 @@
@Before
public void setUp() throws Exception {
- mFeatureFlags.setDefault(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION);
mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
mDependency.injectMockDependency(NotificationMediaManager.class);
allowTestableLooperAsMainThread();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index 3862b0f..f76957f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -61,6 +61,7 @@
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.util.ArraySet;
import android.view.View;
import android.view.WindowInsets;
import android.view.WindowManager;
@@ -71,6 +72,7 @@
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.test.filters.SmallTest;
+import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.systemui.Flags;
@@ -220,6 +222,24 @@
}
@Test
+ @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void triggerDismissMenuAction_callsA11yManagerEnableShortcutsForTargets() {
+ final List<String> stubShortcutTargets = new ArrayList<>();
+ stubShortcutTargets.add(TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString());
+ when(mStubAccessibilityManager.getAccessibilityShortcutTargets(
+ AccessibilityManager.ACCESSIBILITY_BUTTON)).thenReturn(stubShortcutTargets);
+
+ mMenuViewLayer.mDismissMenuAction.run();
+
+ verify(mStubAccessibilityManager).enableShortcutsForTargets(
+ /* enable= */ false,
+ ShortcutConstants.UserShortcutType.SOFTWARE,
+ new ArraySet<>(stubShortcutTargets),
+ mSecureSettings.getRealUserHandle(UserHandle.USER_CURRENT));
+ }
+
+ @Test
+ @DisableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void triggerDismissMenuAction_matchA11yButtonTargetsResult() {
mMenuViewLayer.mDismissMenuAction.run();
verify(mSecureSettings).putStringForUser(
@@ -228,6 +248,7 @@
}
@Test
+ @DisableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void triggerDismissMenuAction_matchEnabledA11yServicesResult() {
setupEnabledAccessibilityServiceList();
@@ -239,6 +260,7 @@
}
@Test
+ @DisableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
public void triggerDismissMenuAction_hasHardwareKeyShortcut_keepEnabledStatus() {
setupEnabledAccessibilityServiceList();
final List<String> stubShortcutTargets = new ArrayList<>();
@@ -286,20 +308,6 @@
}
@Test
- @DisableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
- public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme_old() {
- mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100));
- final PointF beforePosition = mMenuView.getMenuPosition();
-
- dispatchShowingImeInsets();
-
- final float menuBottom = mMenuView.getTranslationY() + mMenuView.getMenuHeight();
- assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x);
- assertThat(menuBottom).isLessThan(beforePosition.y);
- }
-
- @Test
- @EnableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme() {
mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100));
final PointF beforePosition = mMenuView.getMenuPosition();
@@ -315,19 +323,6 @@
}
@Test
- @DisableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
- public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition_old() {
- mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 200));
- final PointF beforePosition = mMenuView.getMenuPosition();
-
- dispatchHidingImeInsets();
-
- assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x);
- assertThat(mMenuView.getTranslationY()).isEqualTo(beforePosition.y);
- }
-
- @Test
- @EnableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition() {
mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 200));
final PointF beforePosition = mMenuView.getMenuPosition();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
index 1ce6525..eced465 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
@@ -22,11 +22,15 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.UiModeManager;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.platform.test.annotations.EnableFlags;
@@ -54,6 +58,8 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.ArrayList;
+
/** Tests for {@link MenuView}. */
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -73,6 +79,8 @@
private AccessibilityManager mAccessibilityManager;
private SysuiTestableContext mSpyContext;
+ @Mock
+ private PackageManager mMockPackageManager;
@Before
public void setUp() throws Exception {
@@ -82,6 +90,8 @@
mSpyContext = spy(mContext);
doNothing().when(mSpyContext).startActivity(any());
+
+ when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
final SecureSettings secureSettings = TestUtils.mockSecureSettings();
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
secureSettings);
@@ -181,10 +191,19 @@
@Test
@EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
public void gotoEditScreen_sendsIntent() {
+ mockActivityQuery(true);
mMenuView.gotoEditScreen();
verify(mSpyContext).startActivity(any());
}
+ @Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void gotoEditScreen_noResolve_doesNotStart() {
+ mockActivityQuery(false);
+ mMenuView.gotoEditScreen();
+ verify(mSpyContext, never()).startActivity(any());
+ }
+
private InstantInsetLayerDrawable getMenuViewInsetLayer() {
return (InstantInsetLayerDrawable) mMenuView.getBackground();
}
@@ -207,4 +226,14 @@
mUiModeManager.setNightMode(mNightMode);
Prefs.putString(mContext, Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, mLastPosition);
}
+
+ private void mockActivityQuery(boolean successfulQuery) {
+ // Query just needs to return a non-empty set to be successful.
+ ArrayList<ResolveInfo> resolveInfos = new ArrayList<>();
+ if (successfulQuery) {
+ resolveInfos.add(new ResolveInfo());
+ }
+ when(mMockPackageManager.queryIntentActivities(
+ any(), any(PackageManager.ResolveInfoFlags.class))).thenReturn(resolveInfos);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 10b86ea..2b4e9ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -20,7 +20,6 @@
import android.hardware.biometrics.BiometricAuthenticator
import android.hardware.biometrics.BiometricConstants
import android.hardware.biometrics.BiometricManager
-import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT
import android.hardware.biometrics.PromptInfo
import android.hardware.biometrics.PromptVerticalListContentView
import android.hardware.face.FaceSensorPropertiesInternal
@@ -386,7 +385,6 @@
@Test
fun testShowCredentialUI_withDescription() {
- mSetFlagsRule.disableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
val container = initializeFingerprintContainer(
authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
)
@@ -397,6 +395,7 @@
}
@Test
+ @Ignore("b/302735104")
fun testShowCredentialUI_withCustomBp() {
val container = initializeFingerprintContainer(
authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
index 54d6b53..67ca9a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
@@ -20,6 +20,7 @@
import com.android.systemui.SysUITestComponent
import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FakeFeatureFlagsClassicModule
import com.android.systemui.flags.Flags
@@ -45,6 +46,7 @@
[
SysUITestModule::class,
UserDomainLayerModule::class,
+ BiometricsDomainLayerModule::class,
]
)
interface TestComponent : SysUITestComponent<AuthDialogPanelInteractionDetector> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
index dc438d7..7808c41 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
@@ -84,8 +84,8 @@
val sensorType by collectLastValue(repository.sensorType)
val sensorLocations by collectLastValue(repository.sensorLocations)
- // Assert default properties.
- assertThat(sensorId).isEqualTo(-1)
+ // Assert non-initialized properties.
+ assertThat(sensorId).isEqualTo(-2)
assertThat(strength).isEqualTo(SensorStrength.CONVENIENCE)
assertThat(sensorType).isEqualTo(FingerprintSensorType.UNKNOWN)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
index 7b972d3..81d4e83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
@@ -17,9 +17,11 @@
package com.android.systemui.biometrics.data.repository
import android.hardware.biometrics.BiometricManager
+import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT
import android.hardware.biometrics.PromptInfo
import android.hardware.biometrics.PromptVerticalListContentView
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.shared.model.PromptKind
@@ -135,6 +137,8 @@
@Test
fun showBpWithoutIconForCredential_withCustomBp() =
testScope.runTest {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CONSTRAINT_BP)
+ mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
for (case in
listOf(
PromptKind.Biometric(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
index fa17672..5caa146 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt
@@ -21,6 +21,7 @@
import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
import com.android.systemui.TestMocksModule
+import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
import com.android.systemui.collectLastValue
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FakeFeatureFlagsClassicModule
@@ -65,6 +66,7 @@
[
SysUITestModule::class,
UserDomainLayerModule::class,
+ BiometricsDomainLayerModule::class,
]
)
interface TestComponent : SysUITestComponent<DefaultUdfpsTouchOverlayViewModel> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 140849b..7db4ca9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -35,6 +35,7 @@
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.Flags.FLAG_BP_TALKBACK
+import com.android.systemui.Flags.FLAG_CONSTRAINT_BP
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.UdfpsUtils
@@ -1256,6 +1257,7 @@
fun descriptionOverriddenByContentView() =
runGenericTest(contentView = promptContentView, description = "test description") {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val contentView by collectLastValue(viewModel.contentView)
val description by collectLastValue(viewModel.description)
@@ -1267,6 +1269,7 @@
fun descriptionWithoutContentView() =
runGenericTest(description = "test description") {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val contentView by collectLastValue(viewModel.contentView)
val description by collectLastValue(viewModel.description)
@@ -1278,6 +1281,7 @@
fun logoIsNullIfPackageNameNotFound() =
runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logo by collectLastValue(viewModel.logo)
assertThat(logo).isNull()
}
@@ -1285,6 +1289,7 @@
@Test
fun defaultLogoIfNoLogoSet() = runGenericTest {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logo by collectLastValue(viewModel.logo)
assertThat(logo).isEqualTo(defaultLogoIcon)
}
@@ -1293,6 +1298,7 @@
fun logoResSetByApp() =
runGenericTest(logoRes = logoResFromApp) {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logo by collectLastValue(viewModel.logo)
assertThat(logo).isEqualTo(logoFromApp)
}
@@ -1301,6 +1307,7 @@
fun logoBitmapSetByApp() =
runGenericTest(logoBitmap = logoBitmapFromApp) {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logo by collectLastValue(viewModel.logo)
assertThat((logo as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp)
}
@@ -1309,6 +1316,7 @@
fun logoDescriptionIsEmptyIfPackageNameNotFound() =
runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logoDescription by collectLastValue(viewModel.logoDescription)
assertThat(logoDescription).isEqualTo("")
}
@@ -1316,6 +1324,7 @@
@Test
fun defaultLogoDescriptionIfNoLogoDescriptionSet() = runGenericTest {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logoDescription by collectLastValue(viewModel.logoDescription)
assertThat(logoDescription).isEqualTo(defaultLogoDescription)
}
@@ -1324,10 +1333,22 @@
fun logoDescriptionSetByApp() =
runGenericTest(logoDescription = logoDescriptionFromApp) {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logoDescription by collectLastValue(viewModel.logoDescription)
assertThat(logoDescription).isEqualTo(logoDescriptionFromApp)
}
+ @Test
+ fun iconViewLoaded() = runGenericTest {
+ val isIconViewLoaded by collectLastValue(viewModel.isIconViewLoaded)
+ // TODO(b/328677869): Add test for noIcon logic.
+ assertThat(isIconViewLoaded).isFalse()
+
+ viewModel.setIsIconViewLoaded(true)
+
+ assertThat(isIconViewLoaded).isTrue()
+ }
+
/** Asserts that the selected buttons are visible now. */
private suspend fun TestScope.assertButtonsVisible(
tryAgain: Boolean = false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
index e796303..701b703 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
@@ -401,7 +401,7 @@
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
Pair("Enter PIN", "PIN is required after lockdown"),
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
- Pair("Enter PIN", "Update will install when device not in use"),
+ Pair("Enter PIN", "PIN required for additional security"),
LockPatternUtils.StrongAuthTracker
.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
Pair(
@@ -439,7 +439,7 @@
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
Pair("Enter PIN", "PIN is required after lockdown"),
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
- Pair("Enter PIN", "Update will install when device not in use"),
+ Pair("Enter PIN", "PIN required for additional security"),
LockPatternUtils.StrongAuthTracker
.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
Pair(
@@ -481,7 +481,7 @@
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
Pair("Enter PIN", "PIN is required after lockdown"),
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
- Pair("Enter PIN", "Update will install when device not in use"),
+ Pair("Enter PIN", "PIN required for additional security"),
LockPatternUtils.StrongAuthTracker
.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
Pair(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index 3f13033..45d20dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -188,6 +188,20 @@
}
@Test
+ public void testRegisterSensor_OccludingActivity() {
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
+
+ ArgumentCaptor<StatusBarStateController.StateListener> stateListenerArgumentCaptor =
+ ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
+ verify(mStatusBarStateController).addCallback(stateListenerArgumentCaptor.capture());
+
+ mFalsingCollector.onScreenTurningOn();
+ reset(mProximitySensor);
+ stateListenerArgumentCaptor.getValue().onStateChanged(StatusBarState.SHADE);
+ verify(mProximitySensor).register(any(ThresholdSensor.Listener.class));
+ }
+
+ @Test
public void testPassThroughGesture() {
MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index e1e9fcb..dac88a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -532,6 +532,18 @@
assertThat(faceAuthRepository.runningAuthRequest.value).isNull()
}
+ @Test
+ fun lockedOut_providesSameValueFromRepository() =
+ testScope.runTest {
+ assertThat(underTest.lockedOut).isSameInstanceAs(faceAuthRepository.isLockedOut)
+ }
+
+ @Test
+ fun authenticated_providesSameValueFromRepository() =
+ testScope.runTest {
+ assertThat(underTest.authenticated).isSameInstanceAs(faceAuthRepository.isAuthenticated)
+ }
+
companion object {
private const val primaryUserId = 1
private val primaryUser = UserInfo(primaryUserId, "test user", UserInfo.FLAG_PRIMARY)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorTest.kt
new file mode 100644
index 0000000..decbdaf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceEntryFingerprintAuthInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.deviceEntryFingerprintAuthInteractor
+ private val fingerprintAuthRepository = kosmos.deviceEntryFingerprintAuthRepository
+ private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
+ private val biometricSettingsRepository = kosmos.biometricSettingsRepository
+
+ @Test
+ fun isFingerprintAuthCurrentlyAllowed_allowedOnlyWhenItIsNotLockedOutAndAllowedBySettings() =
+ testScope.runTest {
+ val currentlyAllowed by collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
+ biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
+ fingerprintAuthRepository.setLockedOut(true)
+
+ assertThat(currentlyAllowed).isFalse()
+
+ fingerprintAuthRepository.setLockedOut(false)
+ assertThat(currentlyAllowed).isTrue()
+
+ biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(false)
+ assertThat(currentlyAllowed).isFalse()
+ }
+
+ @Test
+ fun isSensorUnderDisplay_trueForUdfpsSensorTypes() =
+ testScope.runTest {
+ val isSensorUnderDisplay by collectLastValue(underTest.isSensorUnderDisplay)
+
+ fingerprintPropertyRepository.supportsUdfps()
+ assertThat(isSensorUnderDisplay).isTrue()
+
+ fingerprintPropertyRepository.supportsRearFps()
+ assertThat(isSensorUnderDisplay).isFalse()
+
+ fingerprintPropertyRepository.supportsSideFps()
+ assertThat(isSensorUnderDisplay).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorTest.kt
index e8eda80..d5839b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorTest.kt
@@ -21,9 +21,15 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.fingerprintPropertyInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
@@ -35,6 +41,8 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class DeviceEntryUdfpsInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
private lateinit var fingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository
private lateinit var biometricsRepository: FakeBiometricSettingsRepository
@@ -43,77 +51,85 @@
@Before
fun setUp() {
- fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
- fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
- biometricsRepository = FakeBiometricSettingsRepository()
+ fingerprintPropertyRepository = kosmos.fakeFingerprintPropertyRepository
+ fingerprintAuthRepository = kosmos.fakeDeviceEntryFingerprintAuthRepository
+ biometricsRepository = kosmos.fakeBiometricSettingsRepository
underTest =
DeviceEntryUdfpsInteractor(
- fingerprintPropertyRepository = fingerprintPropertyRepository,
+ fingerprintPropertyInteractor = kosmos.fingerprintPropertyInteractor,
fingerprintAuthRepository = fingerprintAuthRepository,
biometricSettingsRepository = biometricsRepository,
)
}
@Test
- fun udfpsSupported_rearFp_false() = runTest {
- val isUdfpsSupported by collectLastValue(underTest.isUdfpsSupported)
- fingerprintPropertyRepository.supportsRearFps()
- assertThat(isUdfpsSupported).isFalse()
- }
+ fun udfpsSupported_rearFp_false() =
+ testScope.runTest {
+ val isUdfpsSupported by collectLastValue(underTest.isUdfpsSupported)
+ fingerprintPropertyRepository.supportsRearFps()
+ assertThat(isUdfpsSupported).isFalse()
+ }
@Test
- fun udfpsSupoprted() = runTest {
- val isUdfpsSupported by collectLastValue(underTest.isUdfpsSupported)
- fingerprintPropertyRepository.supportsUdfps()
- assertThat(isUdfpsSupported).isTrue()
- }
+ fun udfpsSupoprted() =
+ testScope.runTest {
+ val isUdfpsSupported by collectLastValue(underTest.isUdfpsSupported)
+ fingerprintPropertyRepository.supportsUdfps()
+ assertThat(isUdfpsSupported).isTrue()
+ }
@Test
- fun udfpsEnrolledAndEnabled() = runTest {
- val isUdfpsEnrolledAndEnabled by collectLastValue(underTest.isUdfpsEnrolledAndEnabled)
- fingerprintPropertyRepository.supportsUdfps()
- biometricsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
- assertThat(isUdfpsEnrolledAndEnabled).isTrue()
- }
+ fun udfpsEnrolledAndEnabled() =
+ testScope.runTest {
+ val isUdfpsEnrolledAndEnabled by collectLastValue(underTest.isUdfpsEnrolledAndEnabled)
+ fingerprintPropertyRepository.supportsUdfps()
+ biometricsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ assertThat(isUdfpsEnrolledAndEnabled).isTrue()
+ }
@Test
- fun udfpsEnrolledAndEnabled_rearFp_false() = runTest {
- val isUdfpsEnrolledAndEnabled by collectLastValue(underTest.isUdfpsEnrolledAndEnabled)
- fingerprintPropertyRepository.supportsRearFps()
- biometricsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
- assertThat(isUdfpsEnrolledAndEnabled).isFalse()
- }
+ fun udfpsEnrolledAndEnabled_rearFp_false() =
+ testScope.runTest {
+ val isUdfpsEnrolledAndEnabled by collectLastValue(underTest.isUdfpsEnrolledAndEnabled)
+ fingerprintPropertyRepository.supportsRearFps()
+ biometricsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ assertThat(isUdfpsEnrolledAndEnabled).isFalse()
+ }
@Test
- fun udfpsEnrolledAndEnabled_notEnrolledOrEnabled_false() = runTest {
- val isUdfpsEnrolledAndEnabled by collectLastValue(underTest.isUdfpsEnrolledAndEnabled)
- fingerprintPropertyRepository.supportsUdfps()
- biometricsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
- assertThat(isUdfpsEnrolledAndEnabled).isFalse()
- }
+ fun udfpsEnrolledAndEnabled_notEnrolledOrEnabled_false() =
+ testScope.runTest {
+ val isUdfpsEnrolledAndEnabled by collectLastValue(underTest.isUdfpsEnrolledAndEnabled)
+ fingerprintPropertyRepository.supportsUdfps()
+ biometricsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ assertThat(isUdfpsEnrolledAndEnabled).isFalse()
+ }
@Test
- fun isListeningForUdfps() = runTest {
- val isListeningForUdfps by collectLastValue(underTest.isListeningForUdfps)
- fingerprintPropertyRepository.supportsUdfps()
- fingerprintAuthRepository.setIsRunning(true)
- assertThat(isListeningForUdfps).isTrue()
- }
+ fun isListeningForUdfps() =
+ testScope.runTest {
+ val isListeningForUdfps by collectLastValue(underTest.isListeningForUdfps)
+ fingerprintPropertyRepository.supportsUdfps()
+ fingerprintAuthRepository.setIsRunning(true)
+ assertThat(isListeningForUdfps).isTrue()
+ }
@Test
- fun isListeningForUdfps_rearFp_false() = runTest {
- val isListeningForUdfps by collectLastValue(underTest.isListeningForUdfps)
- fingerprintPropertyRepository.supportsRearFps()
- fingerprintAuthRepository.setIsRunning(true)
- assertThat(isListeningForUdfps).isFalse()
- }
+ fun isListeningForUdfps_rearFp_false() =
+ testScope.runTest {
+ val isListeningForUdfps by collectLastValue(underTest.isListeningForUdfps)
+ fingerprintPropertyRepository.supportsRearFps()
+ fingerprintAuthRepository.setIsRunning(true)
+ assertThat(isListeningForUdfps).isFalse()
+ }
@Test
- fun isListeningForUdfps_notRunning_false() = runTest {
- val isListeningForUdfps by collectLastValue(underTest.isListeningForUdfps)
- fingerprintPropertyRepository.supportsUdfps()
- fingerprintAuthRepository.setIsRunning(false)
- assertThat(isListeningForUdfps).isFalse()
- }
+ fun isListeningForUdfps_notRunning_false() =
+ testScope.runTest {
+ val isListeningForUdfps by collectLastValue(underTest.isListeningForUdfps)
+ fingerprintPropertyRepository.supportsUdfps()
+ fingerprintAuthRepository.setIsRunning(false)
+ assertThat(isListeningForUdfps).isFalse()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 0bd4cbe..1849245 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -93,11 +93,11 @@
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.ui.viewmodel.DreamViewModel;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.SystemPropertiesHelper;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -220,7 +220,7 @@
private boolean mKeyguardGoingAway = false;
private @Mock CoroutineDispatcher mDispatcher;
- private @Mock DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel;
+ private @Mock DreamViewModel mDreamViewModel;
private @Mock SystemPropertiesHelper mSystemPropertiesHelper;
private @Mock SceneContainerFlags mSceneContainerFlags;
@@ -241,9 +241,9 @@
final ViewRootImpl testViewRoot = mock(ViewRootImpl.class);
when(testViewRoot.getView()).thenReturn(mock(View.class));
when(mStatusBarKeyguardViewManager.getViewRootImpl()).thenReturn(testViewRoot);
- when(mDreamingToLockscreenTransitionViewModel.getDreamOverlayAlpha())
+ when(mDreamViewModel.getDreamAlpha())
.thenReturn(mock(Flow.class));
- when(mDreamingToLockscreenTransitionViewModel.getTransitionEnded())
+ when(mDreamViewModel.getTransitionEnded())
.thenReturn(mock(Flow.class));
when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(mDefaultUserId);
when(mSelectedUserInteractor.getSelectedUserId(anyBoolean())).thenReturn(mDefaultUserId);
@@ -1259,7 +1259,7 @@
mSystemSettings,
mSystemClock,
mDispatcher,
- () -> mDreamingToLockscreenTransitionViewModel,
+ () -> mDreamViewModel,
mSystemPropertiesHelper,
() -> mock(WindowManagerLockscreenVisibilityManager.class),
mSelectedUserInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
index 37836a5..bcaad01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
@@ -62,7 +62,6 @@
whenever(defaultLockscreenBlueprint.id).thenReturn(DEFAULT)
underTest =
KeyguardBlueprintRepository(
- configurationRepository,
setOf(defaultLockscreenBlueprint),
fakeExecutorHandler,
threadAssert,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
index df52265..0bd541c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
@@ -20,16 +20,19 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
+import com.android.systemui.testKosmos
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.runTest
import org.junit.Before
import org.junit.Test
@@ -43,41 +46,35 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class BurnInInteractorTest : SysuiTestCase() {
+ val kosmos = testKosmos()
+ val testScope = kosmos.testScope
+ val configurationRepository = kosmos.fakeConfigurationRepository
+ val fakeKeyguardRepository = kosmos.fakeKeyguardRepository
+
private val burnInOffset = 7
private var burnInProgress = 0f
@Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
- private lateinit var configurationRepository: FakeConfigurationRepository
- private lateinit var keyguardInteractor: KeyguardInteractor
- private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
- private lateinit var testScope: TestScope
private lateinit var underTest: BurnInInteractor
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- configurationRepository = FakeConfigurationRepository()
context
.getOrCreateTestableResources()
.addOverride(R.dimen.burn_in_prevention_offset_y, burnInOffset)
-
- KeyguardInteractorFactory.create().let {
- keyguardInteractor = it.keyguardInteractor
- fakeKeyguardRepository = it.repository
- }
whenever(burnInHelperWrapper.burnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset)
setBurnInProgress(.65f)
- testScope = TestScope()
underTest =
BurnInInteractor(
context,
burnInHelperWrapper,
- testScope.backgroundScope,
- configurationRepository,
- keyguardInteractor,
+ kosmos.applicationCoroutineScope,
+ kosmos.configurationInteractor,
+ kosmos.keyguardInteractor,
)
}
@@ -122,7 +119,13 @@
testScope.runTest {
whenever(burnInHelperWrapper.burnInScale()).thenReturn(0.5f)
- val burnInModel by collectLastValue(underTest.keyguardBurnIn)
+ val burnInModel by
+ collectLastValue(
+ underTest.burnIn(
+ xDimenResourceId = R.dimen.burn_in_prevention_offset_x,
+ yDimenResourceId = R.dimen.burn_in_prevention_offset_y
+ )
+ )
// After time tick, returns the configured values
fakeKeyguardRepository.dozeTimeTick(10)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
index b0d8de3..170d348 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt
@@ -21,95 +21,80 @@
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository
+import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.WEATHER_CLOCK_BLUEPRINT_ID
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint
-import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition
-import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config
-import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.clocks.ClockConfig
import com.android.systemui.plugins.clocks.ClockController
-import com.android.systemui.statusbar.policy.SplitShadeStateController
-import com.android.systemui.util.mockito.any
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@ExperimentalCoroutinesApi
@SmallTest
@RunWith(AndroidJUnit4::class)
class KeyguardBlueprintInteractorTest : SysuiTestCase() {
- private val configurationFlow = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
- private lateinit var underTest: KeyguardBlueprintInteractor
- private lateinit var testScope: TestScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest by lazy { kosmos.keyguardBlueprintInteractor }
+ private val clockRepository by lazy { kosmos.fakeKeyguardClockRepository }
+ private val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
+ private val fingerprintPropertyRepository by lazy { kosmos.fakeFingerprintPropertyRepository }
- val refreshTransition: MutableSharedFlow<IntraBlueprintTransition.Config> =
- MutableSharedFlow(extraBufferCapacity = 1)
-
- @Mock private lateinit var splitShadeStateController: SplitShadeStateController
- @Mock private lateinit var keyguardBlueprintRepository: KeyguardBlueprintRepository
- @Mock private lateinit var clockInteractor: KeyguardClockInteractor
@Mock private lateinit var clockController: ClockController
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- testScope = TestScope(StandardTestDispatcher())
- whenever(keyguardBlueprintRepository.configurationChange).thenReturn(configurationFlow)
- whenever(keyguardBlueprintRepository.refreshTransition).thenReturn(refreshTransition)
- whenever(clockInteractor.currentClock).thenReturn(MutableStateFlow(clockController))
- clockInteractor.currentClock
-
- underTest =
- KeyguardBlueprintInteractor(
- keyguardBlueprintRepository,
- testScope.backgroundScope,
- mContext,
- splitShadeStateController,
- clockInteractor,
- )
}
@Test
fun testAppliesDefaultBlueprint() {
testScope.runTest {
- whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
- .thenReturn(false)
-
- reset(keyguardBlueprintRepository)
- configurationFlow.tryEmit(Unit)
+ val blueprint by collectLastValue(underTest.blueprint)
+ overrideResource(R.bool.config_use_split_notification_shade, false)
+ configurationRepository.onConfigurationChange()
runCurrent()
- verify(keyguardBlueprintRepository)
- .applyBlueprint(DefaultKeyguardBlueprint.Companion.DEFAULT)
+ assertThat(blueprint?.id).isEqualTo(DefaultKeyguardBlueprint.Companion.DEFAULT)
}
}
@Test
fun testAppliesSplitShadeBlueprint() {
testScope.runTest {
- whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
- .thenReturn(true)
-
- reset(keyguardBlueprintRepository)
- configurationFlow.tryEmit(Unit)
+ val blueprint by collectLastValue(underTest.blueprint)
+ overrideResource(R.bool.config_use_split_notification_shade, true)
+ configurationRepository.onConfigurationChange()
runCurrent()
- verify(keyguardBlueprintRepository)
- .applyBlueprint(SplitShadeKeyguardBlueprint.Companion.ID)
+ assertThat(blueprint?.id).isEqualTo(SplitShadeKeyguardBlueprint.Companion.ID)
+ }
+ }
+
+ @Test
+ fun fingerprintPropertyInitialized_updatesBlueprint() {
+ testScope.runTest {
+ val blueprint by collectLastValue(underTest.blueprint)
+ overrideResource(R.bool.config_use_split_notification_shade, true)
+ fingerprintPropertyRepository.supportsUdfps() // initialize properties
+ runCurrent()
+ assertThat(blueprint?.id).isEqualTo(SplitShadeKeyguardBlueprint.Companion.ID)
}
}
@@ -117,6 +102,7 @@
fun composeLockscreenOff_DoesAppliesSplitShadeWeatherClockBlueprint() {
testScope.runTest {
mSetFlagsRule.disableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
+ val blueprint by collectLastValue(underTest.blueprint)
whenever(clockController.config)
.thenReturn(
ClockConfig(
@@ -125,15 +111,12 @@
description = "clock",
)
)
- whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
- .thenReturn(true)
-
- reset(keyguardBlueprintRepository)
- configurationFlow.tryEmit(Unit)
+ clockRepository.setCurrentClock(clockController)
+ overrideResource(R.bool.config_use_split_notification_shade, true)
+ configurationRepository.onConfigurationChange()
runCurrent()
- verify(keyguardBlueprintRepository, never())
- .applyBlueprint(SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID)
+ assertThat(blueprint?.id).isNotEqualTo(SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID)
}
}
@@ -141,6 +124,7 @@
fun testDoesAppliesSplitShadeWeatherClockBlueprint() {
testScope.runTest {
mSetFlagsRule.enableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
+ val blueprint by collectLastValue(underTest.blueprint)
whenever(clockController.config)
.thenReturn(
ClockConfig(
@@ -149,15 +133,12 @@
description = "clock",
)
)
- whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
- .thenReturn(true)
-
- reset(keyguardBlueprintRepository)
- configurationFlow.tryEmit(Unit)
+ clockRepository.setCurrentClock(clockController)
+ overrideResource(R.bool.config_use_split_notification_shade, true)
+ configurationRepository.onConfigurationChange()
runCurrent()
- verify(keyguardBlueprintRepository)
- .applyBlueprint(SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID)
+ assertThat(blueprint?.id).isEqualTo(SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID)
}
}
@@ -165,6 +146,7 @@
fun testAppliesWeatherClockBlueprint() {
testScope.runTest {
mSetFlagsRule.enableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN)
+ val blueprint by collectLastValue(underTest.blueprint)
whenever(clockController.config)
.thenReturn(
ClockConfig(
@@ -173,33 +155,12 @@
description = "clock",
)
)
- whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
- .thenReturn(false)
-
- reset(keyguardBlueprintRepository)
- configurationFlow.tryEmit(Unit)
+ clockRepository.setCurrentClock(clockController)
+ overrideResource(R.bool.config_use_split_notification_shade, false)
+ configurationRepository.onConfigurationChange()
runCurrent()
- verify(keyguardBlueprintRepository).applyBlueprint(WEATHER_CLOCK_BLUEPRINT_ID)
+ assertThat(blueprint?.id).isEqualTo(WEATHER_CLOCK_BLUEPRINT_ID)
}
}
-
- @Test
- fun testRefreshBlueprint() {
- underTest.refreshBlueprint()
- verify(keyguardBlueprintRepository).refreshBlueprint()
- }
-
- @Test
- fun testTransitionToBlueprint() {
- underTest.transitionToBlueprint("abc")
- verify(keyguardBlueprintRepository).applyBlueprint("abc")
- }
-
- @Test
- fun testRefreshBlueprintWithTransition() {
- underTest.refreshBlueprint(Type.DefaultTransition)
- verify(keyguardBlueprintRepository)
- .refreshBlueprint(Config(Type.DefaultTransition, true, true))
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
index 87eee1a..0a29821 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
@@ -22,23 +22,25 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.doze.util.BurnInHelperWrapper
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
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.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -53,18 +55,19 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class UdfpsKeyguardInteractorTest : SysuiTestCase() {
+ val kosmos = testKosmos()
+ val testScope = kosmos.testScope
+ val configRepository = kosmos.fakeConfigurationRepository
+ val keyguardRepository = kosmos.fakeKeyguardRepository
+
private val burnInProgress = 1f
private val burnInYOffset = 20
private val burnInXOffset = 10
- private lateinit var testScope: TestScope
- private lateinit var configRepository: FakeConfigurationRepository
private lateinit var bouncerRepository: KeyguardBouncerRepository
- private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var fakeCommandQueue: FakeCommandQueue
private lateinit var burnInInteractor: BurnInInteractor
private lateinit var shadeRepository: FakeShadeRepository
- private lateinit var keyguardInteractor: KeyguardInteractor
private lateinit var powerInteractor: PowerInteractor
@Mock private lateinit var burnInHelper: BurnInHelperWrapper
@@ -75,12 +78,6 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- testScope = TestScope()
- configRepository = FakeConfigurationRepository()
- KeyguardInteractorFactory.create().let {
- keyguardInteractor = it.keyguardInteractor
- keyguardRepository = it.repository
- }
bouncerRepository = FakeKeyguardBouncerRepository()
shadeRepository = FakeShadeRepository()
fakeCommandQueue = FakeCommandQueue()
@@ -89,8 +86,8 @@
context,
burnInHelper,
testScope.backgroundScope,
- configRepository,
- keyguardInteractor
+ kosmos.configurationInteractor,
+ kosmos.keyguardInteractor
)
powerInteractor = PowerInteractorFactory.create().powerInteractor
@@ -98,7 +95,7 @@
UdfpsKeyguardInteractor(
configRepository,
burnInInteractor,
- keyguardInteractor,
+ kosmos.keyguardInteractor,
shadeRepository,
dialogManager,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
index 699284e..09c56b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
@@ -22,7 +22,6 @@
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.LockIconViewController
import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
@@ -37,8 +36,10 @@
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import org.junit.Before
import org.junit.Test
@@ -53,13 +54,13 @@
@RunWith(JUnit4::class)
@SmallTest
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
@Mock private lateinit var notificationPanelView: NotificationPanelView
private lateinit var featureFlags: FakeFeatureFlags
@Mock private lateinit var lockIconViewController: LockIconViewController
@Mock private lateinit var falsingManager: FalsingManager
+ @Mock private lateinit var deviceEntryIconViewModel: DeviceEntryIconViewModel
private lateinit var underTest: DefaultDeviceEntrySection
@Before
@@ -73,14 +74,13 @@
underTest =
DefaultDeviceEntrySection(
TestScope().backgroundScope,
- keyguardUpdateMonitor,
authController,
windowManager,
context,
notificationPanelView,
featureFlags,
{ lockIconViewController },
- { mock(DeviceEntryIconViewModel::class.java) },
+ { deviceEntryIconViewModel },
{ mock(DeviceEntryForegroundViewModel::class.java) },
{ mock(DeviceEntryBackgroundViewModel::class.java) },
{ falsingManager },
@@ -129,6 +129,7 @@
@Test
fun applyConstraints_udfps_refactor_on() {
mSetFlagsRule.enableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+ whenever(deviceEntryIconViewModel.isUdfpsSupported).thenReturn(MutableStateFlow(false))
val cs = ConstraintSet()
underTest.applyConstraints(cs)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
index 22a2e93..f252163 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -130,6 +130,24 @@
assertThat(value()).isEqualTo(LARGE)
}
+ @Test
+ fun isLargeClockVisible_whenLargeClockSize_isTrue() =
+ scope.runTest {
+ fakeSettings.putInt(LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1)
+ keyguardClockRepository.setClockSize(LARGE)
+ var value = collectLastValue(underTest.isLargeClockVisible)
+ assertThat(value()).isEqualTo(true)
+ }
+
+ @Test
+ fun isLargeClockVisible_whenSmallClockSize_isFalse() =
+ scope.runTest {
+ fakeSettings.putInt(LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1)
+ keyguardClockRepository.setClockSize(SMALL)
+ var value = collectLastValue(underTest.isLargeClockVisible)
+ assertThat(value()).isEqualTo(false)
+ }
+
private fun setupMockClock() {
whenever(clock.largeClock).thenReturn(largeClock)
whenever(largeClock.config).thenReturn(clockFaceConfig)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
deleted file mode 100644
index 864acfb..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.ui.viewmodel
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.Flags as AConfigFlags
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(JUnit4::class)
-class KeyguardIndicationAreaViewModelTest : SysuiTestCase() {
-
- @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
- @Mock private lateinit var shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel
-
- private lateinit var underTest: KeyguardIndicationAreaViewModel
- private lateinit var repository: FakeKeyguardRepository
-
- private val startButtonFlow =
- MutableStateFlow<KeyguardQuickAffordanceViewModel>(
- KeyguardQuickAffordanceViewModel(
- slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()
- )
- )
- private val endButtonFlow =
- MutableStateFlow<KeyguardQuickAffordanceViewModel>(
- KeyguardQuickAffordanceViewModel(
- slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId()
- )
- )
- private val alphaFlow = MutableStateFlow<Float>(1f)
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
-
- whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
- .thenReturn(RETURNED_BURN_IN_OFFSET)
-
- val withDeps = KeyguardInteractorFactory.create()
- val keyguardInteractor = withDeps.keyguardInteractor
- repository = withDeps.repository
-
- val bottomAreaViewModel: KeyguardBottomAreaViewModel = mock()
- whenever(bottomAreaViewModel.startButton).thenReturn(startButtonFlow)
- whenever(bottomAreaViewModel.endButton).thenReturn(endButtonFlow)
- whenever(bottomAreaViewModel.alpha).thenReturn(alphaFlow)
- underTest =
- KeyguardIndicationAreaViewModel(
- keyguardInteractor = keyguardInteractor,
- bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
- keyguardBottomAreaViewModel = bottomAreaViewModel,
- burnInHelperWrapper = burnInHelperWrapper,
- shortcutsCombinedViewModel = shortcutsCombinedViewModel,
- configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()),
- )
- }
-
- @Test
- fun alpha() = runTest {
- val value = collectLastValue(underTest.alpha)
-
- assertThat(value()).isEqualTo(1f)
- alphaFlow.value = 0.1f
- assertThat(value()).isEqualTo(0.1f)
- alphaFlow.value = 0.5f
- assertThat(value()).isEqualTo(0.5f)
- alphaFlow.value = 0.2f
- assertThat(value()).isEqualTo(0.2f)
- alphaFlow.value = 0f
- assertThat(value()).isEqualTo(0f)
- }
-
- @Test
- fun isIndicationAreaPadded() = runTest {
- repository.setKeyguardShowing(true)
- val value = collectLastValue(underTest.isIndicationAreaPadded)
-
- assertThat(value()).isFalse()
- startButtonFlow.value = startButtonFlow.value.copy(isVisible = true)
- assertThat(value()).isTrue()
- endButtonFlow.value = endButtonFlow.value.copy(isVisible = true)
- assertThat(value()).isTrue()
- startButtonFlow.value = startButtonFlow.value.copy(isVisible = false)
- assertThat(value()).isTrue()
- endButtonFlow.value = endButtonFlow.value.copy(isVisible = false)
- assertThat(value()).isFalse()
- }
-
- @Test
- fun indicationAreaTranslationX() = runTest {
- val value = collectLastValue(underTest.indicationAreaTranslationX)
-
- assertThat(value()).isEqualTo(0f)
- repository.setClockPosition(100, 100)
- assertThat(value()).isEqualTo(100f)
- repository.setClockPosition(200, 100)
- assertThat(value()).isEqualTo(200f)
- repository.setClockPosition(200, 200)
- assertThat(value()).isEqualTo(200f)
- repository.setClockPosition(300, 100)
- assertThat(value()).isEqualTo(300f)
- }
-
- @Test
- fun indicationAreaTranslationY() = runTest {
- val value = collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET))
-
- // Negative 0 - apparently there's a difference in floating point arithmetic - FML
- assertThat(value()).isEqualTo(-0f)
- val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f)
- assertThat(value()).isEqualTo(expected1)
- val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f)
- assertThat(value()).isEqualTo(expected2)
- val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f)
- assertThat(value()).isEqualTo(expected3)
- val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f)
- assertThat(value()).isEqualTo(expected4)
- }
-
- private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
- repository.setDozeAmount(dozeAmount)
- return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET)
- }
-
- companion object {
- private const val DEFAULT_BURN_IN_OFFSET = 5
- private const val RETURNED_BURN_IN_OFFSET = 3
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index bcec6109..b80dcd4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -62,6 +62,7 @@
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth
import kotlin.math.min
+import kotlin.test.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
@@ -77,7 +78,6 @@
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
-import kotlin.test.assertEquals
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -134,7 +134,12 @@
private lateinit var lockscreenToPrimaryBouncerTransitionViewModel:
LockscreenToPrimaryBouncerTransitionViewModel
@Mock
- private lateinit var transitionInteractor: KeyguardTransitionInteractor
+ private lateinit var lockscreenToGlanceableHubTransitionViewModel:
+ LockscreenToGlanceableHubTransitionViewModel
+ @Mock
+ private lateinit var glanceableHubToLockscreenTransitionViewModel:
+ GlanceableHubToLockscreenTransitionViewModel
+ @Mock private lateinit var transitionInteractor: KeyguardTransitionInteractor
private lateinit var underTest: KeyguardQuickAffordancesCombinedViewModel
@@ -271,6 +276,10 @@
whenever(lockscreenToOccludedTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow())
whenever(lockscreenToPrimaryBouncerTransitionViewModel.shortcutsAlpha)
.thenReturn(emptyFlow())
+ whenever(lockscreenToGlanceableHubTransitionViewModel.shortcutsAlpha)
+ .thenReturn(emptyFlow())
+ whenever(glanceableHubToLockscreenTransitionViewModel.shortcutsAlpha)
+ .thenReturn(emptyFlow())
whenever(shadeInteractor.anyExpansion).thenReturn(intendedShadeAlphaMutableStateFlow)
whenever(transitionInteractor.finishedKeyguardState)
.thenReturn(intendedFinishedKeyguardStateFlow)
@@ -307,6 +316,8 @@
offToLockscreenTransitionViewModel = offToLockscreenTransitionViewModel,
primaryBouncerToLockscreenTransitionViewModel =
primaryBouncerToLockscreenTransitionViewModel,
+ glanceableHubToLockscreenTransitionViewModel =
+ glanceableHubToLockscreenTransitionViewModel,
lockscreenToAodTransitionViewModel = lockscreenToAodTransitionViewModel,
lockscreenToDozingTransitionViewModel = lockscreenToDozingTransitionViewModel,
lockscreenToDreamingHostedTransitionViewModel =
@@ -316,6 +327,8 @@
lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel,
lockscreenToPrimaryBouncerTransitionViewModel =
lockscreenToPrimaryBouncerTransitionViewModel,
+ lockscreenToGlanceableHubTransitionViewModel =
+ lockscreenToGlanceableHubTransitionViewModel,
transitionInteractor = transitionInteractor,
)
}
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 ca403e0..695d3b2 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
@@ -19,7 +19,6 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -48,7 +47,6 @@
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
-import com.android.settingslib.media.LocalMediaManager;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.broadcast.BroadcastSender;
@@ -129,12 +127,6 @@
mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
mKeyguardManager, mFlags, mUserTracker);
- // Using a fake package will cause routing operations to fail, so we intercept
- // scanning-related operations.
- mMediaOutputController.mLocalMediaManager = mock(LocalMediaManager.class);
- doNothing().when(mMediaOutputController.mLocalMediaManager).startScan();
- doNothing().when(mMediaOutputController.mLocalMediaManager).stopScan();
-
mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
mMediaOutputController);
mMediaOutputBaseDialogImpl.onCreate(new Bundle());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 31746a2..b38d5e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -99,6 +99,7 @@
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shared.rotation.RotationButtonController;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.CommandQueue;
@@ -580,6 +581,7 @@
() -> Optional.of(mCentralSurfaces),
mKeyguardStateController,
mock(ShadeViewController.class),
+ mock(PanelExpansionInteractor.class),
mock(NotificationRemoteInputManager.class),
mock(NotificationShadeDepthController.class),
mHandler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index 65ede89..0101741 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -51,6 +51,7 @@
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import com.android.systemui.util.animation.DisappearParameters;
@@ -100,6 +101,8 @@
Configuration mConfiguration;
@Mock
Runnable mHorizontalLayoutListener;
+ @Mock
+ VibratorHelper mVibratorHelper;
private QSPanelControllerBase<QSPanel> mController;
@@ -110,7 +113,8 @@
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
DumpManager dumpManager) {
super(view, host, qsCustomizerController, true, mediaHost, metricsLogger, uiEventLogger,
- qsLogger, dumpManager, new ResourcesSplitShadeStateController());
+ qsLogger, dumpManager, new ResourcesSplitShadeStateController(),
+ mVibratorHelper);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 85d7d98..916e8dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -19,6 +19,7 @@
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import com.android.systemui.settings.brightness.BrightnessController
import com.android.systemui.settings.brightness.BrightnessSliderController
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.tuner.TunerService
@@ -61,6 +62,7 @@
@Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@Mock private lateinit var configuration: Configuration
@Mock private lateinit var pagedTileLayout: PagedTileLayout
+ @Mock private lateinit var vibratorHelper: VibratorHelper
private val sceneContainerFlags = FakeSceneContainerFlags()
@@ -101,6 +103,7 @@
statusBarKeyguardViewManager,
ResourcesSplitShadeStateController(),
sceneContainerFlags,
+ vibratorHelper,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index 2c14308..71a9a8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.customize.QSCustomizerController
import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.util.leak.RotationUtils
import org.junit.After
@@ -59,6 +60,7 @@
@Mock private lateinit var tile: QSTile
@Mock private lateinit var tileLayout: TileLayout
@Captor private lateinit var captor: ArgumentCaptor<QSPanel.OnConfigurationChangedListener>
+ @Mock private lateinit var vibratorHelper: VibratorHelper
private val uiEventLogger = UiEventLoggerFake()
private val dumpManager = DumpManager()
@@ -89,7 +91,8 @@
metricsLogger,
uiEventLogger,
qsLogger,
- dumpManager
+ dumpManager,
+ vibratorHelper,
)
controller.init()
@@ -157,7 +160,8 @@
metricsLogger: MetricsLogger,
uiEventLogger: UiEventLoggerFake,
qsLogger: QSLogger,
- dumpManager: DumpManager
+ dumpManager: DumpManager,
+ vibratorHelper: VibratorHelper,
) :
QuickQSPanelController(
view,
@@ -170,7 +174,8 @@
uiEventLogger,
qsLogger,
dumpManager,
- ResourcesSplitShadeStateController()
+ ResourcesSplitShadeStateController(),
+ vibratorHelper,
) {
private var rotation = RotationUtils.ROTATION_NONE
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index ada93db..2e8160b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -37,7 +37,6 @@
import com.android.systemui.model.SysUiState
import com.android.systemui.qs.tiles.RecordIssueTile
import com.android.systemui.res.R
-import com.android.systemui.settings.UserContextProvider
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -71,12 +70,11 @@
@Mock private lateinit var devicePolicyResolver: ScreenCaptureDevicePolicyResolver
@Mock private lateinit var dprLazy: dagger.Lazy<ScreenCaptureDevicePolicyResolver>
@Mock private lateinit var mediaProjectionMetricsLogger: MediaProjectionMetricsLogger
- @Mock private lateinit var userContextProvider: UserContextProvider
@Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var userFileManager: UserFileManager
@Mock private lateinit var sharedPreferences: SharedPreferences
- @Mock private lateinit var screenCaptureDisabledDialogDelegate:
- ScreenCaptureDisabledDialogDelegate
+ @Mock
+ private lateinit var screenCaptureDisabledDialogDelegate: ScreenCaptureDisabledDialogDelegate
@Mock private lateinit var screenCaptureDisabledDialog: SystemUIDialog
@Mock private lateinit var sysuiState: SysUiState
@@ -96,9 +94,8 @@
MockitoAnnotations.initMocks(this)
whenever(dprLazy.get()).thenReturn(devicePolicyResolver)
whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState)
- whenever(userContextProvider.userContext).thenReturn(mContext)
whenever(screenCaptureDisabledDialogDelegate.createDialog())
- .thenReturn(screenCaptureDisabledDialog)
+ .thenReturn(screenCaptureDisabledDialog)
whenever(
userFileManager.getSharedPreferences(
eq(RecordIssueTile.TILE_SPEC),
@@ -123,7 +120,6 @@
dialog =
RecordIssueDialogDelegate(
factory,
- userContextProvider,
userTracker,
flags,
bgExecutor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
index 6068c23..f4d7a5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.screenshot;
+import static com.android.systemui.screenshot.ImageExporter.createSystemFileDisplayName;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -182,6 +184,30 @@
}
@Test
+ public void testImageExport_customizedFileName()
+ throws ExecutionException, InterruptedException {
+ // This test only asserts the file name for the case when user specifies a file name,
+ // instead of using the auto-generated name by ImageExporter::createFileName. Other
+ // metadata are not affected by the specified file name.
+ final String customizedFileName = "customized_file_name";
+ ContentResolver contentResolver = mContext.getContentResolver();
+ ImageExporter exporter = new ImageExporter(contentResolver, mFeatureFlags);
+
+ UUID requestId = UUID.fromString("3c11da99-9284-4863-b1d5-6f3684976814");
+ Bitmap original = createCheckerBitmap(10, 10, 10);
+
+ ListenableFuture<ImageExporter.Result> direct =
+ exporter.export(DIRECT_EXECUTOR, requestId, original, CAPTURE_TIME,
+ Process.myUserHandle(), customizedFileName);
+ assertTrue("future should be done", direct.isDone());
+ assertFalse("future should not be canceled", direct.isCancelled());
+ ImageExporter.Result result = direct.get();
+ assertEquals("Filename should contain the correct filename",
+ createSystemFileDisplayName(customizedFileName, CompressFormat.PNG),
+ result.fileName);
+ }
+
+ @Test
public void testMediaStoreMetadata() {
String name = ImageExporter.createFilename(CAPTURE_TIME, CompressFormat.PNG,
Display.DEFAULT_DISPLAY);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
index 3dc9037..0baee5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -274,8 +274,8 @@
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
screenshotExecutor.onCloseSystemDialogsReceived()
- verify(controller0).dismissScreenshot(any())
- verify(controller1).dismissScreenshot(any())
+ verify(controller0).requestDismissal(any())
+ verify(controller1).requestDismissal(any())
screenshotExecutor.onDestroy()
}
@@ -290,8 +290,8 @@
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
screenshotExecutor.onCloseSystemDialogsReceived()
- verify(controller0, never()).dismissScreenshot(any())
- verify(controller1).dismissScreenshot(any())
+ verify(controller0, never()).requestDismissal(any())
+ verify(controller1).requestDismissal(any())
screenshotExecutor.onDestroy()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
index ab90b9b..25ba09a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.BrightnessMirrorController
import com.android.systemui.util.mockito.any
@@ -66,6 +67,8 @@
private lateinit var listener: ToggleSlider.Listener
@Mock
private lateinit var vibratorHelper: VibratorHelper
+ @Mock
+ private lateinit var activityStarter: ActivityStarter
@Captor
private lateinit var seekBarChangeCaptor: ArgumentCaptor<SeekBar.OnSeekBarChangeListener>
@@ -91,6 +94,7 @@
mFalsingManager,
uiEventLogger,
SeekableSliderHapticPlugin(vibratorHelper, systemClock),
+ activityStarter,
)
mController.init()
mController.setOnChangedListener(listener)
@@ -120,7 +124,7 @@
@Test
fun testEnforceAdminRelayed() {
mController.setEnforcedAdmin(enforcedAdmin)
- verify(brightnessSliderView).setEnforcedAdmin(enforcedAdmin)
+ verify(brightnessSliderView).setAdminBlocker(notNull())
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 62d2d0e..07d9350 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -42,6 +42,7 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
@@ -76,6 +77,7 @@
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock private lateinit var shadeInteractor: ShadeInteractor
@Mock private lateinit var powerManager: PowerManager
+ @Mock private lateinit var dialogFactory: SystemUIDialogFactory
private lateinit var parentView: FrameLayout
private lateinit var containerView: View
@@ -99,6 +101,7 @@
GlanceableHubContainerController(
communalInteractor,
communalViewModel,
+ dialogFactory,
keyguardTransitionInteractor,
shadeInteractor,
powerManager
@@ -138,6 +141,7 @@
GlanceableHubContainerController(
communalInteractor,
communalViewModel,
+ dialogFactory,
keyguardTransitionInteractor,
shadeInteractor,
powerManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index acbf997..43fcdf3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -38,6 +38,7 @@
import static org.mockito.Mockito.when;
import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
import android.annotation.IdRes;
import android.content.ContentResolver;
@@ -213,7 +214,6 @@
import java.util.Optional;
import kotlinx.coroutines.CoroutineDispatcher;
-import kotlinx.coroutines.flow.StateFlowKt;
import kotlinx.coroutines.test.TestScope;
public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
@@ -409,10 +409,10 @@
new ShadeAnimationRepository(), mShadeRepository);
mPowerInteractor = keyguardInteractorDeps.getPowerInteractor();
when(mKeyguardTransitionInteractor.isInTransitionToStateWhere(any())).thenReturn(
- StateFlowKt.MutableStateFlow(false));
+ MutableStateFlow(false));
DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor =
mock(DeviceEntryUdfpsInteractor.class);
- when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(emptyFlow());
+ when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(MutableStateFlow(false));
mShadeInteractor = new ShadeInteractorImpl(
mTestScope.getBackgroundScope(),
@@ -461,7 +461,6 @@
mKeyguardLogger,
mInteractionJankMonitor,
mKeyguardInteractor,
- mKeyguardTransitionInteractor,
mDumpManager,
mPowerInteractor));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index d24fe1b..6d5d5be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -854,7 +854,7 @@
// We are interested in the last value of the stack alpha.
ArgumentCaptor<Float> alphaCaptor = ArgumentCaptor.forClass(Float.class);
verify(mNotificationStackScrollLayoutController, atLeastOnce())
- .setMaxAlphaForExpansion(alphaCaptor.capture());
+ .setMaxAlphaForKeyguard(alphaCaptor.capture(), any());
assertThat(alphaCaptor.getValue()).isEqualTo(1.0f);
}
@@ -875,7 +875,7 @@
// We are interested in the last value of the stack alpha.
ArgumentCaptor<Float> alphaCaptor = ArgumentCaptor.forClass(Float.class);
verify(mNotificationStackScrollLayoutController, atLeastOnce())
- .setMaxAlphaForExpansion(alphaCaptor.capture());
+ .setMaxAlphaForKeyguard(alphaCaptor.capture(), any());
assertThat(alphaCaptor.getValue()).isEqualTo(0.0f);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 3808d30..c31c625 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -36,7 +36,7 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
import android.app.IActivityManager;
import android.content.pm.ActivityInfo;
@@ -205,7 +205,7 @@
DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor =
mock(DeviceEntryUdfpsInteractor.class);
- when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(emptyFlow());
+ when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(MutableStateFlow(false));
mShadeInteractor = new ShadeInteractorImpl(
mTestScope.getBackgroundScope(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 617b25d..88b239a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -481,8 +481,7 @@
// AND status bar doesn't want it
whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT))
.thenReturn(false)
- // AND shade is not fully expanded
- whenever(notificationPanelViewController.isFullyExpanded()).thenReturn(false)
+ // AND shade is not fully expanded (mock is false by default)
// AND the lock icon does NOT want the touch
whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(false)
// AND quick settings controller DOES want it
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 4809a47..a077164 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -22,7 +22,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
import android.content.res.Resources;
@@ -234,7 +234,8 @@
DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor =
mock(DeviceEntryUdfpsInteractor.class);
- when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(emptyFlow());
+ when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(
+ MutableStateFlow(false));
mShadeInteractor = new ShadeInteractorImpl(
mTestScope.getBackgroundScope(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
index 2f957b0..0a9541a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
@@ -18,7 +18,7 @@
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
-import com.android.systemui.scene.domain.interactor.PanelExpansionInteractor
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.FakeSceneDataSource
@@ -27,8 +27,8 @@
import com.android.systemui.shade.STATE_OPENING
import com.android.systemui.shade.ShadeExpansionChangeEvent
import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.shade.domain.interactor.panelExpansionInteractor
import com.android.systemui.statusbar.SysuiStatusBarStateController
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.panelExpansionInteractor
import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.testKosmos
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 86116a0..d35c7dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -9,6 +9,7 @@
import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
import com.android.systemui.TestMocksModule
+import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.dagger.SysUISingleton
@@ -35,6 +36,8 @@
import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.user.domain.UserDomainLayerModule
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import dagger.BindsInstance
import dagger.Component
@@ -55,6 +58,7 @@
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.clearInvocations
@@ -310,6 +314,17 @@
}
@Test
+ fun testGoToLockedShadeCancelDoesntLeaveShadeOpenOnKeyguardHide() {
+ whenever(lockScreenUserManager.shouldShowLockscreenNotifications()).thenReturn(false)
+ whenever(lockScreenUserManager.isLockscreenPublicMode(any())).thenReturn(true)
+ transitionController.goToLockedShade(null)
+ val captor = argumentCaptor<Runnable>()
+ verify(centralSurfaces).showBouncerWithDimissAndCancelIfKeyguard(isNull(), captor.capture())
+ captor.value.run()
+ verify(statusbarStateController).setLeaveOpenOnKeyguardHide(false)
+ }
+
+ @Test
fun testDragDownAmountDoesntCallOutInLockedDownShade() {
whenever(nsslController.isInLockedDownShade).thenReturn(true)
transitionController.dragDownAmount = 10f
@@ -611,6 +626,7 @@
[
SysUITestModule::class,
UserDomainLayerModule::class,
+ BiometricsDomainLayerModule::class,
]
)
interface TestComponent {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index dfbb6ea..103dcb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -54,6 +54,7 @@
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -69,7 +70,7 @@
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
@@ -85,9 +86,8 @@
import org.mockito.Mockito
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import com.android.systemui.scene.shared.model.Scenes
import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -155,7 +155,7 @@
{ kosmos.sceneInteractor },
)
- whenever(deviceEntryUdfpsInteractor.isUdfpsSupported).thenReturn(emptyFlow())
+ whenever(deviceEntryUdfpsInteractor.isUdfpsSupported).thenReturn(MutableStateFlow(false))
shadeInteractor =
ShadeInteractorImpl(
testScope.backgroundScope,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
index b548117..e90a3ac8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
@@ -157,52 +158,52 @@
}
@Test
- @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS)
+ @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS)
public void inflateMediaNotificationIconsMediaEnabled_old() throws InflationException {
finishSetupWithMediaFeatureFlagEnabled(true);
mListener.onEntryInit(mMediaEntry);
mListener.onEntryAdded(mMediaEntry);
verify(mIconManager, never()).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
mFilter.shouldFilterOut(mMediaEntry, 0);
verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
mFilter.shouldFilterOut(mMediaEntry, 0);
verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
- verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry), /* usingCache = */ eq(false));
mListener.onEntryRemoved(mMediaEntry, NotificationListenerService.REASON_CANCEL);
mListener.onEntryCleanUp(mMediaEntry);
mListener.onEntryInit(mMediaEntry);
verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
- verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry), /* usingCache = */ eq(false));
mFilter.shouldFilterOut(mMediaEntry, 0);
verify(mIconManager, times(2)).createIcons(eq(mMediaEntry));
- verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry), /* usingCache = */ eq(false));
}
@Test
- @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS)
+ @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS)
public void inflateMediaNotificationIconsMediaEnabled_new() throws InflationException {
finishSetupWithMediaFeatureFlagEnabled(true);
mListener.onEntryInit(mMediaEntry);
mListener.onEntryAdded(mMediaEntry);
verify(mIconManager).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
clearInvocations(mIconManager);
mFilter.shouldFilterOut(mMediaEntry, 0);
verify(mIconManager, never()).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
mListener.onEntryUpdated(mMediaEntry);
verify(mIconManager, never()).createIcons(eq(mMediaEntry));
- verify(mIconManager).updateIcons(eq(mMediaEntry));
+ verify(mIconManager).updateIcons(eq(mMediaEntry), /* usingCache = */ eq(false));
mListener.onEntryRemoved(mMediaEntry, NotificationListenerService.REASON_CANCEL);
mListener.onEntryCleanUp(mMediaEntry);
@@ -211,40 +212,40 @@
mListener.onEntryInit(mMediaEntry);
mListener.onEntryAdded(mMediaEntry);
verify(mIconManager).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
}
@Test
- @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS)
+ @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS)
public void inflationException_old() throws InflationException {
finishSetupWithMediaFeatureFlagEnabled(true);
mListener.onEntryInit(mMediaEntry);
mListener.onEntryAdded(mMediaEntry);
verify(mIconManager, never()).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
doThrow(InflationException.class).when(mIconManager).createIcons(eq(mMediaEntry));
mFilter.shouldFilterOut(mMediaEntry, 0);
verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
mFilter.shouldFilterOut(mMediaEntry, 0);
verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), /* usingCache = */ eq(false));
mListener.onEntryUpdated(mMediaEntry);
verify(mIconManager, times(1)).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
doNothing().when(mIconManager).createIcons(eq(mMediaEntry));
mFilter.shouldFilterOut(mMediaEntry, 0);
verify(mIconManager, times(2)).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
}
@Test
- @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS)
+ @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS)
public void inflationException_new() throws InflationException {
finishSetupWithMediaFeatureFlagEnabled(true);
@@ -253,19 +254,19 @@
mListener.onEntryInit(mMediaEntry);
mListener.onEntryAdded(mMediaEntry);
verify(mIconManager).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
clearInvocations(mIconManager);
mListener.onEntryUpdated(mMediaEntry);
verify(mIconManager).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
clearInvocations(mIconManager);
doNothing().when(mIconManager).createIcons(eq(mMediaEntry));
mListener.onEntryUpdated(mMediaEntry);
verify(mIconManager).createIcons(eq(mMediaEntry));
- verify(mIconManager, never()).updateIcons(eq(mMediaEntry));
+ verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean());
}
private void finishSetupWithMediaFeatureFlagEnabled(boolean mediaFeatureFlagEnabled) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
index 4519ba6..419b0fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
@@ -68,6 +68,7 @@
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.collection.render.NotifViewBarn;
import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager;
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
@@ -107,6 +108,7 @@
@Mock private IStatusBarService mService;
@Mock private BindEventManagerImpl mBindEventManagerImpl;
@Mock private NotificationLockscreenUserManager mLockscreenUserManager;
+ @Mock private SensitiveNotificationProtectionController mSensitiveNotifProtectionController;
@Mock private Handler mHandler;
@Mock private SecureSettings mSecureSettings;
@Spy private FakeNotifInflater mNotifInflater = new FakeNotifInflater();
@@ -128,6 +130,7 @@
mHandler,
mSecureSettings,
mLockscreenUserManager,
+ mSensitiveNotifProtectionController,
mSectionStyleProvider,
mUserTracker,
mGroupMembershipManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
index 457d2f0..018a571 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
@@ -94,6 +94,26 @@
}
@Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun onSensitiveStateChanged_invokeInvalidationListener() {
+ coordinator.attach(pipeline)
+ val invalidator =
+ withArgCaptor<Invalidator> { verify(pipeline).addPreRenderInvalidator(capture()) }
+ val onSensitiveStateChangedListener =
+ withArgCaptor<Runnable> {
+ verify(sensitiveNotificationProtectionController)
+ .registerSensitiveStateListener(capture())
+ }
+
+ val invalidationListener = mock<Pluggable.PluggableListener<Invalidator>>()
+ invalidator.setInvalidationListener(invalidationListener)
+
+ onSensitiveStateChangedListener.run()
+
+ verify(invalidationListener).onPluggableInvalidated(eq(invalidator), any())
+ }
+
+ @Test
fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction() {
coordinator.attach(pipeline)
val onBeforeRenderListListener =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
index 115a0d3..34eeba0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
@@ -23,6 +23,7 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
+import com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
import com.android.systemui.SysuiTestCase
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -33,6 +34,7 @@
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation
import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
@@ -57,6 +59,8 @@
@RunWithLooper
class NotifUiAdjustmentProviderTest : SysuiTestCase() {
private val lockscreenUserManager: NotificationLockscreenUserManager = mock()
+ private val sensitiveNotifProtectionController: SensitiveNotificationProtectionController =
+ mock()
private val sectionStyleProvider: SectionStyleProvider = mock()
private val handler: Handler = mock()
private val secureSettings: SecureSettings = mock()
@@ -77,6 +81,7 @@
handler,
secureSettings,
lockscreenUserManager,
+ sensitiveNotifProtectionController,
sectionStyleProvider,
userTracker,
groupMembershipManager,
@@ -108,6 +113,19 @@
}
@Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun sensitiveNotifProtectionStateChangeWillNotifDirty() {
+ val dirtyListener = mock<Runnable>()
+ adjustmentProvider.addDirtyListener(dirtyListener)
+ val sensitiveStateChangedListener =
+ withArgCaptor<Runnable> {
+ verify(sensitiveNotifProtectionController).registerSensitiveStateListener(capture())
+ }
+ sensitiveStateChangedListener.run()
+ verify(dirtyListener).run()
+ }
+
+ @Test
fun additionalAddDoesNotRegisterAgain() {
clearInvocations(secureSettings)
adjustmentProvider.addDirtyListener(mock())
@@ -199,4 +217,38 @@
// Then: Need re-inflation
assertTrue(NotifUiAdjustment.needReinflate(oldAdjustment, newAdjustment))
}
+
+ @Test
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun changeSensitiveNotifProtection_screenshareNotificationHidingEnabled_needReinflate() {
+ whenever(sensitiveNotifProtectionController.shouldProtectNotification(entry))
+ .thenReturn(false)
+ val oldAdjustment: NotifUiAdjustment = adjustmentProvider.calculateAdjustment(entry)
+ assertFalse(oldAdjustment.needsRedaction)
+
+ whenever(sensitiveNotifProtectionController.shouldProtectNotification(entry))
+ .thenReturn(true)
+ val newAdjustment = adjustmentProvider.calculateAdjustment(entry)
+ assertTrue(newAdjustment.needsRedaction)
+
+ // Then: need re-inflation
+ assertTrue(NotifUiAdjustment.needReinflate(oldAdjustment, newAdjustment))
+ }
+
+ @Test
+ @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+ fun changeSensitiveNotifProtection_screenshareNotificationHidingDisabled_noNeedReinflate() {
+ whenever(sensitiveNotifProtectionController.shouldProtectNotification(entry))
+ .thenReturn(false)
+ val oldAdjustment = adjustmentProvider.calculateAdjustment(entry)
+ assertFalse(oldAdjustment.needsRedaction)
+
+ whenever(sensitiveNotifProtectionController.shouldProtectNotification(entry))
+ .thenReturn(true)
+ val newAdjustment = adjustmentProvider.calculateAdjustment(entry)
+ assertFalse(newAdjustment.needsRedaction)
+
+ // Then: need no re-inflation
+ assertFalse(NotifUiAdjustment.needReinflate(oldAdjustment, newAdjustment))
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
index a12806b..4ac9dc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.statusbar.notification.icon
import android.app.ActivityManager
@@ -38,6 +40,10 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
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.runCurrent
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -69,6 +75,11 @@
@Mock private lateinit var notifCollection: CommonNotifCollection
@Mock private lateinit var launcherApps: LauncherApps
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+ private val mainContext = testScope.coroutineContext
+ private val bgContext = testScope.backgroundScope.coroutineContext
+
private val iconBuilder = IconBuilder(context)
private lateinit var iconManager: IconManager
@@ -85,7 +96,15 @@
`when`(shortcut.icon).thenReturn(shortcutIc)
`when`(launcherApps.getShortcutIcon(shortcut)).thenReturn(shortcutIc)
- iconManager = IconManager(notifCollection, launcherApps, iconBuilder)
+ iconManager =
+ IconManager(
+ notifCollection,
+ launcherApps,
+ iconBuilder,
+ testScope,
+ bgContext,
+ mainContext,
+ )
}
@Test
@@ -94,6 +113,7 @@
notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = true)
entry?.channel?.isImportantConversation = true
entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc)
}
@@ -103,6 +123,7 @@
notificationEntry(hasShortcut = false, hasMessageSenderIcon = true, hasLargeIcon = true)
entry?.channel?.isImportantConversation = true
entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(messageIc)
}
@@ -116,6 +137,7 @@
)
entry?.channel?.isImportantConversation = true
entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(largeIc)
}
@@ -129,6 +151,7 @@
)
entry?.channel?.isImportantConversation = true
entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(smallIc)
}
@@ -143,6 +166,7 @@
)
entry?.channel?.isImportantConversation = true
entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(smallIc)
}
@@ -161,6 +185,7 @@
entry?.setSensitive(true, true)
entry?.channel?.isImportantConversation = true
entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc)
assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(smallIc)
assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc)
@@ -175,6 +200,7 @@
entry?.let { iconManager.createIcons(it) }
// Updating the icons after creation shouldn't break anything
entry?.let { iconManager.updateIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc)
assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(smallIc)
assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc)
@@ -187,9 +213,11 @@
entry?.channel?.isImportantConversation = true
entry?.setSensitive(true, true)
entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc)
entry?.setSensitive(false, false)
entry?.let { iconManager.updateIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(shortcutIc)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 42a6924..b114e13 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -60,7 +60,6 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestableContext;
import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
@@ -104,7 +103,6 @@
TestableLooper.get(this),
mFeatureFlags);
mNotificationTestHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL);
- mFeatureFlags.setDefault(Flags.SENSITIVE_REVEAL_ANIM);
}
@Test
@@ -186,14 +184,6 @@
}
@Test
- public void testSetSensitiveOnNotifRowNotifiesOfHeightChange_withOtherFlagValue()
- throws Exception {
- FakeFeatureFlags flags = mFeatureFlags;
- flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM));
- testSetSensitiveOnNotifRowNotifiesOfHeightChange();
- }
-
- @Test
public void testSetSensitiveOnNotifRowNotifiesOfHeightChange() throws Exception {
// GIVEN a sensitive notification row that's currently redacted
ExpandableNotificationRow row = mNotificationTestHelper.createRow();
@@ -210,19 +200,10 @@
// WHEN the row is set to no longer be sensitive
row.setSensitive(false, true);
- boolean expectAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
// VERIFY that the height change listener is invoked
assertThat(row.getShowingLayout()).isSameInstanceAs(row.getPrivateLayout());
assertThat(row.getIntrinsicHeight()).isGreaterThan(0);
- verify(listener).onHeightChanged(eq(row), eq(expectAnimation));
- }
-
- @Test
- public void testSetSensitiveOnGroupRowNotifiesOfHeightChange_withOtherFlagValue()
- throws Exception {
- FakeFeatureFlags flags = mFeatureFlags;
- flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM));
- testSetSensitiveOnGroupRowNotifiesOfHeightChange();
+ verify(listener).onHeightChanged(eq(row), eq(true));
}
@Test
@@ -242,19 +223,10 @@
// WHEN the row is set to no longer be sensitive
group.setSensitive(false, true);
- boolean expectAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
// VERIFY that the height change listener is invoked
assertThat(group.getShowingLayout()).isSameInstanceAs(group.getPrivateLayout());
assertThat(group.getIntrinsicHeight()).isGreaterThan(0);
- verify(listener).onHeightChanged(eq(group), eq(expectAnimation));
- }
-
- @Test
- public void testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange_withOtherFlagValue()
- throws Exception {
- FakeFeatureFlags flags = mFeatureFlags;
- flags.set(Flags.SENSITIVE_REVEAL_ANIM, !flags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM));
- testSetSensitiveOnPublicRowDoesNotNotifyOfHeightChange();
+ verify(listener).onHeightChanged(eq(group), eq(true));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 1763d9b..3c1f559 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.row;
+import static android.app.Notification.EXTRA_BUILDER_APPLICATION_INFO;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
@@ -183,8 +184,6 @@
.thenReturn(packageInfo);
final ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.uid = TEST_UID; // non-zero
- when(mMockPackageManager.getApplicationInfo(eq(TEST_PACKAGE_NAME), anyInt())).thenReturn(
- applicationInfo);
final PackageInfo systemPackageInfo = new PackageInfo();
systemPackageInfo.packageName = TEST_SYSTEM_PACKAGE_NAME;
when(mMockPackageManager.getPackageInfo(eq(TEST_SYSTEM_PACKAGE_NAME), anyInt()))
@@ -207,6 +206,7 @@
.addMessage(new Notification.MessagingStyle.Message(
"hello!", 1000, new Person.Builder().setName("other").build())))
.build();
+ notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, applicationInfo);
mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
notification, UserHandle.CURRENT, null, 0);
mEntry = new NotificationEntryBuilder().setSbn(mSbn).setShortcutInfo(mShortcutInfo).build();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index b59385c..f31b1c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.row;
+import static android.app.Notification.EXTRA_BUILDER_APPLICATION_INFO;
import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_LOW;
@@ -143,8 +144,6 @@
.thenReturn(packageInfo);
final ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.uid = TEST_UID; // non-zero
- when(mMockPackageManager.getApplicationInfo(eq(TEST_PACKAGE_NAME), anyInt())).thenReturn(
- applicationInfo);
final PackageInfo systemPackageInfo = new PackageInfo();
systemPackageInfo.packageName = TEST_SYSTEM_PACKAGE_NAME;
when(mMockPackageManager.getPackageInfo(eq(TEST_SYSTEM_PACKAGE_NAME), anyInt()))
@@ -162,8 +161,10 @@
mDefaultNotificationChannel = new NotificationChannel(
NotificationChannel.DEFAULT_CHANNEL_ID, TEST_CHANNEL_NAME,
IMPORTANCE_LOW);
+ Notification notification = new Notification();
+ notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, applicationInfo);
mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
- new Notification(), UserHandle.getUserHandleForUid(TEST_UID), null, 0);
+ notification, UserHandle.getUserHandleForUid(TEST_UID), null, 0);
mEntry = new NotificationEntryBuilder().setSbn(mSbn).build();
when(mAssistantFeedbackController.isFeedbackEnabled()).thenReturn(false);
when(mAssistantFeedbackController.getInlineDescriptionResource(any()))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 718f998..3b78b7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -53,6 +53,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
+import com.android.keyguard.TestScopeProvider;
import com.android.systemui.TestableDependency;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.flags.FakeFeatureFlags;
@@ -102,6 +103,9 @@
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
+import kotlin.coroutines.CoroutineContext;
+import kotlinx.coroutines.test.TestScope;
+
/**
* A helper class to create {@link ExpandableNotificationRow} (for both individual and group
* notifications).
@@ -140,6 +144,10 @@
private final FakeFeatureFlags mFeatureFlags;
private final SystemClock mSystemClock;
private final RowInflaterTaskLogger mRowInflaterTaskLogger;
+ private final TestScope mTestScope = TestScopeProvider.getTestScope();
+ private final CoroutineContext mBgCoroutineContext =
+ mTestScope.getBackgroundScope().getCoroutineContext();
+ private final CoroutineContext mMainCoroutineContext = mTestScope.getCoroutineContext();
public NotificationTestHelper(
Context context,
@@ -169,7 +177,10 @@
mIconManager = new IconManager(
mock(CommonNotifCollection.class),
mock(LauncherApps.class),
- new IconBuilder(mContext));
+ new IconBuilder(mContext),
+ mTestScope,
+ mBgCoroutineContext,
+ mMainCoroutineContext);
NotificationContentInflater contentBinder = new NotificationContentInflater(
mock(NotifRemoteViewCache.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index b938029..9a7b8ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -10,7 +10,6 @@
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.res.R
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.NotificationShelf
@@ -23,7 +22,6 @@
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
-import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -38,7 +36,6 @@
@RunWithLooper
open class NotificationShelfTest : SysuiTestCase() {
- open val useSensitiveReveal: Boolean = false
private val flags = FakeFeatureFlags()
@Mock private lateinit var largeScreenShadeInterpolator: LargeScreenShadeInterpolator
@@ -53,7 +50,6 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
mDependency.injectTestDependency(FeatureFlags::class.java, flags)
- flags.set(Flags.SENSITIVE_REVEAL_ANIM, useSensitiveReveal)
val root = FrameLayout(context)
shelf =
LayoutInflater.from(root.context)
@@ -335,7 +331,6 @@
@Test
fun updateState_withNullLastVisibleBackgroundChild_hideShelf() {
// GIVEN
- assumeTrue(useSensitiveReveal)
whenever(ambientState.stackY).thenReturn(100f)
whenever(ambientState.stackHeight).thenReturn(100f)
val paddingBetweenElements =
@@ -362,7 +357,6 @@
@Test
fun updateState_withNullFirstViewInShelf_hideShelf() {
// GIVEN
- assumeTrue(useSensitiveReveal)
whenever(ambientState.stackY).thenReturn(100f)
whenever(ambientState.stackHeight).thenReturn(100f)
val paddingBetweenElements =
@@ -389,7 +383,6 @@
@Test
fun updateState_withCollapsedShade_hideShelf() {
// GIVEN
- assumeTrue(useSensitiveReveal)
whenever(ambientState.stackY).thenReturn(100f)
whenever(ambientState.stackHeight).thenReturn(100f)
val paddingBetweenElements =
@@ -416,7 +409,6 @@
@Test
fun updateState_withHiddenSectionBeforeShelf_hideShelf() {
// GIVEN
- assumeTrue(useSensitiveReveal)
whenever(ambientState.stackY).thenReturn(100f)
whenever(ambientState.stackHeight).thenReturn(100f)
val paddingBetweenElements =
@@ -476,10 +468,3 @@
assertEquals(expectedAlpha, shelf.viewState.alpha)
}
}
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@RunWithLooper
-class NotificationShelfWithSensitiveRevealTest : NotificationShelfTest() {
- override val useSensitiveReveal: Boolean = true
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 220305c..13df091 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -165,8 +165,6 @@
// TODO: Ideally we wouldn't need to set these unless a test actually reads them,
// and then we would test both configurations, but currently they are all read
// in the constructor.
- mFeatureFlags.setDefault(Flags.SENSITIVE_REVEAL_ANIM);
- mFeatureFlags.setDefault(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS);
mSetFlagsRule.enableFlags(FLAG_NEW_AOD_TRANSITION);
mFeatureFlags.setDefault(Flags.UNCLEARED_TRANSIENT_HUN_FIX);
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 84156ee1..c8c54dbd 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
@@ -859,6 +859,29 @@
}
@Test
+ public void testEnteringGlanceableHub_whenDreaming_updatesScrim() {
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
+ when(mKeyguardUpdateMonitor.isDreaming()).thenReturn(true);
+
+ // Transition to the glanceable hub.
+ mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle(
+ CommunalScenes.Communal)));
+ mTestScope.getTestScheduler().runCurrent();
+
+ // ScrimState also transitions.
+ verify(mScrimController).transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
+
+ // Transition away from the glanceable hub.
+ mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle(
+ CommunalScenes.Blank)));
+ mTestScope.getTestScheduler().runCurrent();
+
+ // ScrimState goes back to UNLOCKED.
+ verify(mScrimController).transitionTo(eq(ScrimState.DREAMING));
+ }
+
+ @Test
public void testShowKeyguardImplementation_setsState() {
when(mLockscreenUserManager.getCurrentProfiles()).thenReturn(new SparseArray<>());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 56fc7b9..1748cff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -22,6 +22,7 @@
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.android.systemui.Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND;
import static com.google.common.truth.Truth.assertThat;
@@ -38,6 +39,8 @@
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -226,7 +229,22 @@
}
@Test
- public void onViewAttached_callbacksRegistered() {
+ @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+ public void onViewAttached_updateUserSwitcherFlagEnabled_callbacksRegistered() {
+ mController.onViewAttached();
+
+ runAllScheduled();
+ verify(mConfigurationController).addCallback(any());
+ verify(mAnimationScheduler).addCallback(any());
+ verify(mUserInfoController).addCallback(any());
+ verify(mCommandQueue).addCallback(any());
+ verify(mStatusBarIconController).addIconGroup(any());
+ verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
+ }
+
+ @Test
+ @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+ public void onViewAttached_updateUserSwitcherFlagDisabled_callbacksRegistered() {
mController.onViewAttached();
verify(mConfigurationController).addCallback(any());
@@ -238,7 +256,26 @@
}
@Test
- public void onConfigurationChanged_updatesUserSwitcherVisibility() {
+ @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+ public void
+ onConfigurationChanged_updateUserSwitcherFlagEnabled_updatesUserSwitcherVisibility() {
+ mController.onViewAttached();
+ runAllScheduled();
+ verify(mConfigurationController).addCallback(mConfigurationListenerCaptor.capture());
+ clearInvocations(mUserManager);
+ clearInvocations(mKeyguardStatusBarView);
+
+ mConfigurationListenerCaptor.getValue().onConfigChanged(null);
+
+ runAllScheduled();
+ verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
+ verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
+ }
+
+ @Test
+ @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+ public void
+ onConfigurationChanged_updateUserSwitcherFlagDisabled_updatesUserSwitcherVisibility() {
mController.onViewAttached();
verify(mConfigurationController).addCallback(mConfigurationListenerCaptor.capture());
clearInvocations(mUserManager);
@@ -250,7 +287,26 @@
}
@Test
- public void onKeyguardVisibilityChanged_updatesUserSwitcherVisibility() {
+ @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+ public void
+ onKeyguardVisibilityChanged_userSwitcherFlagEnabled_updatesUserSwitcherVisibility() {
+ mController.onViewAttached();
+ runAllScheduled();
+ verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardCallbackCaptor.capture());
+ clearInvocations(mUserManager);
+ clearInvocations(mKeyguardStatusBarView);
+
+ mKeyguardCallbackCaptor.getValue().onKeyguardVisibilityChanged(true);
+
+ runAllScheduled();
+ verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
+ verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
+ }
+
+ @Test
+ @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+ public void
+ onKeyguardVisibilityChanged_userSwitcherFlagDisabled_updatesUserSwitcherVisibility() {
mController.onViewAttached();
verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardCallbackCaptor.capture());
clearInvocations(mUserManager);
@@ -298,7 +354,7 @@
verify(mStatusBarIconController).addIconGroup(any());
}
-
+
@Test
public void setBatteryListening_true_callbackAdded() {
mController.setBatteryListening(true);
@@ -762,6 +818,11 @@
return captor.getValue();
}
+ private void runAllScheduled() {
+ mBackgroundExecutor.runAllReady();
+ mFakeExecutor.runAllReady();
+ }
+
private static class TestShadeViewStateProvider
implements ShadeViewStateProvider {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 99c2dc7..f947640 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -747,14 +747,16 @@
// Open the bouncer.
mScrimController.setRawPanelExpansionFraction(0f);
+ when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_VISIBLE);
finishAnimationsImmediately();
- // Only behind widget is visible.
+ // Only behind scrim is visible.
assertScrimAlpha(Map.of(
mScrimInFront, TRANSPARENT,
mNotificationsScrim, TRANSPARENT,
mScrimBehind, OPAQUE));
+ assertScrimTint(mScrimBehind, mSurfaceColor);
// Bouncer is closed.
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
@@ -773,8 +775,9 @@
mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB);
// Open the shade.
- mScrimController.transitionTo(SHADE_LOCKED);
mScrimController.setQsPosition(1f, 0);
+ mScrimController.setRawPanelExpansionFraction(1);
+ mScrimController.setTransitionToFullShadeProgress(1, 0);
finishAnimationsImmediately();
// Shade scrims are visible.
@@ -782,8 +785,81 @@
mNotificationsScrim, OPAQUE,
mScrimInFront, TRANSPARENT,
mScrimBehind, OPAQUE));
+ assertScrimTint(mScrimBehind, Color.BLACK);
+ assertScrimTint(mNotificationsScrim, Color.TRANSPARENT);
- mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB);
+ mScrimController.setTransitionToFullShadeProgress(0, 0);
+ finishAnimationsImmediately();
+
+ // All scrims are transparent.
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, TRANSPARENT,
+ mScrimBehind, TRANSPARENT));
+ }
+
+ @Test
+ public void transitionToHubOverDream() {
+ mScrimController.setRawPanelExpansionFraction(0f);
+ mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
+ mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
+ finishAnimationsImmediately();
+
+ // All scrims transparent on the hub.
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, TRANSPARENT,
+ mScrimBehind, TRANSPARENT));
+ }
+
+ @Test
+ public void openBouncerOnHubOverDream() {
+ mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
+
+ // Open the bouncer.
+ mScrimController.setRawPanelExpansionFraction(0f);
+ when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
+ mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_VISIBLE);
+ finishAnimationsImmediately();
+
+ // Only behind scrim is visible.
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, TRANSPARENT,
+ mScrimBehind, OPAQUE));
+ assertScrimTint(mScrimBehind, mSurfaceColor);
+
+ // Bouncer is closed.
+ mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
+ mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
+ finishAnimationsImmediately();
+
+ // All scrims are transparent.
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, TRANSPARENT,
+ mScrimBehind, TRANSPARENT));
+ }
+
+ @Test
+ public void openShadeOnHubOverDream() {
+ mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
+
+ // Open the shade.
+ mScrimController.setQsPosition(1f, 0);
+ mScrimController.setRawPanelExpansionFraction(1f);
+ finishAnimationsImmediately();
+
+ // Shade scrims are visible.
+ assertScrimAlpha(Map.of(
+ mNotificationsScrim, OPAQUE,
+ mScrimInFront, TRANSPARENT,
+ mScrimBehind, OPAQUE));
+ assertScrimTint(mScrimBehind, Color.BLACK);
+ assertScrimTint(mNotificationsScrim, Color.TRANSPARENT);
+
+ mScrimController.setQsPosition(0f, 0);
+ mScrimController.setRawPanelExpansionFraction(0f);
finishAnimationsImmediately();
// All scrims are transparent.
@@ -1432,7 +1508,8 @@
ScrimState.UNINITIALIZED, ScrimState.KEYGUARD, BOUNCER,
ScrimState.DREAMING, ScrimState.BOUNCER_SCRIMMED, ScrimState.BRIGHTNESS_MIRROR,
ScrimState.UNLOCKED, SHADE_LOCKED, ScrimState.AUTH_SCRIMMED,
- ScrimState.AUTH_SCRIMMED_SHADE, ScrimState.GLANCEABLE_HUB));
+ ScrimState.AUTH_SCRIMMED_SHADE, ScrimState.GLANCEABLE_HUB,
+ ScrimState.GLANCEABLE_HUB_OVER_DREAM));
for (ScrimState state : ScrimState.values()) {
if (!lowPowerModeStates.contains(state) && !regularStates.contains(state)) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index b0404a0..a8c5fc3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -50,6 +50,7 @@
import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -296,6 +297,7 @@
mStatusBarNotificationPresenter = new StatusBarNotificationPresenter(
mContext,
shadeViewController,
+ mock(PanelExpansionInteractor.class),
mock(QuickSettingsController.class),
mock(HeadsUpManager.class),
notificationShadeWindowView,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index 6a0375d..3bf54a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -31,6 +31,7 @@
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.StatusBarStateControllerImpl
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.settings.GlobalSettings
import junit.framework.Assert.assertFalse
@@ -80,6 +81,8 @@
@Mock
private lateinit var handler: Handler
+ val kosmos = testKosmos()
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -87,14 +90,14 @@
context,
wakefulnessLifecycle,
statusBarStateController,
- dagger.Lazy<KeyguardViewMediator> { keyguardViewMediator },
+ { keyguardViewMediator },
keyguardStateController,
- dagger.Lazy<DozeParameters> { dozeParameters },
+ { dozeParameters },
globalSettings,
- dagger.Lazy<NotificationShadeWindowController> { notifShadeWindowController },
+ { notifShadeWindowController },
interactionJankMonitor,
powerManager,
- handler = handler,
+ handler = handler
)
controller.initialize(centralSurfaces, shadeViewController, lightRevealScrim)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 13167b2..c259782 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -71,7 +71,6 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -455,7 +454,6 @@
private RemoteInputViewController bindController(
RemoteInputView view,
NotificationEntry entry) {
- mFeatureFlags.set(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION, true);
RemoteInputViewControllerImpl viewController = new RemoteInputViewControllerImpl(
view,
entry,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/FoldableTestUtils.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/FoldableTestUtils.kt
index e499a3c..e4a1c26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/FoldableTestUtils.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/FoldableTestUtils.kt
@@ -30,16 +30,22 @@
assumeTrue("Test should be launched on a foldable device",
foldedDeviceStates.isNotEmpty())
- val folded =
- DeviceState(foldedDeviceStates.maxOrNull()!! /* identifier */,
- "" /* name */,
- emptySet() /* properties */)
- val unfolded =
- DeviceState(folded.identifier + 1 /* identifier */,
- "" /* name */,
- emptySet() /* properties */)
+ val folded = getDeviceState(
+ identifier = foldedDeviceStates.maxOrNull()!!
+ )
+ val unfolded = getDeviceState(
+ identifier = folded.identifier + 1
+ )
return FoldableDeviceStates(folded = folded, unfolded = unfolded)
}
+
+ private fun getDeviceState(identifier: Int): DeviceState {
+ return DeviceState(
+ DeviceState.Configuration.Builder(
+ identifier, "" /* name */
+ ).build()
+ )
+ }
}
data class FoldableDeviceStates(val folded: DeviceState, val unfolded: DeviceState)
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index d0261ae..5206db4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -20,6 +20,7 @@
import static android.media.AudioManager.RINGER_MODE_SILENT;
import static android.media.AudioManager.RINGER_MODE_VIBRATE;
+import static com.android.systemui.Flags.FLAG_HAPTIC_VOLUME_SLIDER;
import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN;
import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN;
import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;
@@ -46,7 +47,10 @@
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
+import android.media.AudioSystem;
import android.os.SystemClock;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -91,6 +95,7 @@
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;
@@ -265,6 +270,55 @@
}
@Test
+ @DisableFlags(FLAG_HAPTIC_VOLUME_SLIDER)
+ public void testVolumeChange_noSliderHaptics_doesNotDeliverOnProgressChangedHaptics() {
+ // Initialize the dialog again with haptic sliders disabled
+ mDialog.init(0, null);
+ final State shellState = createShellState();
+ VolumeDialogController.StreamState musicStreamState =
+ shellState.states.get(AudioSystem.STREAM_MUSIC);
+
+ mDialog.show(SHOW_REASON_UNKNOWN);
+ mTestableLooper.processMessages(1); //Only the SHOW message
+
+ // Change the volume two times
+ musicStreamState.level += 10;
+ mDialog.onStateChangedH(shellState);
+ mAnimatorTestRule.advanceTimeBy(10);
+ musicStreamState.level += 10;
+ mDialog.onStateChangedH(shellState);
+
+ // expected: the type of the progress haptics for the stream should be DISABLED
+ short type = mDialog.progressHapticsForStream(AudioSystem.STREAM_MUSIC);
+ assertEquals(VolumeDialogImpl.PROGRESS_HAPTICS_DISABLED, type);
+ }
+
+ @Ignore("Causing breakages so ignoring to resolve, b/329099861")
+ @Test
+ @EnableFlags(FLAG_HAPTIC_VOLUME_SLIDER)
+ public void testVolumeChange_withSliderHaptics_deliversOnProgressChangedHapticsEagerly() {
+ // Initialize the dialog again to create haptic plugins on the rows with the flag enabled
+ mDialog.init(0, null);
+ final State shellState = createShellState();
+ VolumeDialogController.StreamState musicStreamState =
+ shellState.states.get(AudioSystem.STREAM_MUSIC);
+
+ mDialog.show(SHOW_REASON_UNKNOWN);
+ mTestableLooper.processMessages(1); //Only the SHOW message
+
+ // Change the volume two times
+ musicStreamState.level += 10;
+ mDialog.onStateChangedH(shellState);
+ mAnimatorTestRule.advanceTimeBy(10);
+ musicStreamState.level += 10;
+ mDialog.onStateChangedH(shellState);
+
+ // expected: the type of the progress haptics for the stream should be EAGER
+ short type = mDialog.progressHapticsForStream(AudioSystem.STREAM_MUSIC);
+ assertEquals(VolumeDialogImpl.PROGRESS_HAPTICS_EAGER, type);
+ }
+
+ @Test
public void testComputeTimeout() {
Mockito.reset(mAccessibilityMgr);
mDialog.rescheduleTimeoutH();
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 fbefb0e..19f31d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -50,7 +50,7 @@
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
import android.app.ActivityManager;
import android.app.IActivityManager;
@@ -90,6 +90,7 @@
import android.view.ViewTreeObserver;
import android.view.WindowManager;
+import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import com.android.internal.colorextraction.ColorExtractor;
@@ -196,6 +197,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.onehanded.OneHandedController;
@@ -450,7 +452,7 @@
DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor =
mock(DeviceEntryUdfpsInteractor.class);
- when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(emptyFlow());
+ when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(MutableStateFlow(false));
mShadeInteractor =
new ShadeInteractorImpl(
@@ -2251,6 +2253,30 @@
verify(mBubbleController).onSensitiveNotificationProtectionStateChanged(false);
}
+ @Test
+ public void setBubbleBarLocation_listenerNotified() {
+ mBubbleProperties.mIsBubbleBarEnabled = true;
+ mPositioner.setIsLargeScreen(true);
+
+ FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+ mBubbleController.registerBubbleStateListener(bubbleStateListener);
+ mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT);
+ assertThat(bubbleStateListener.mLastUpdate).isNotNull();
+ assertThat(bubbleStateListener.mLastUpdate.bubbleBarLocation).isEqualTo(
+ BubbleBarLocation.LEFT);
+ }
+
+ @Test
+ public void setBubbleBarLocation_barDisabled_shouldBeIgnored() {
+ mBubbleProperties.mIsBubbleBarEnabled = false;
+ mPositioner.setIsLargeScreen(true);
+
+ FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+ mBubbleController.registerBubbleStateListener(bubbleStateListener);
+ mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT);
+ assertThat(bubbleStateListener.mStateChangeCalls).isEqualTo(0);
+ }
+
/** Creates a bubble using the userId and package. */
private Bubble createBubble(int userId, String pkg) {
final UserHandle userHandle = new UserHandle(userId);
@@ -2436,8 +2462,15 @@
}
private static class FakeBubbleStateListener implements Bubbles.BubbleStateListener {
+
+ int mStateChangeCalls = 0;
+ @Nullable
+ BubbleBarUpdate mLastUpdate;
+
@Override
public void onBubbleStateChange(BubbleBarUpdate update) {
+ mStateChangeCalls++;
+ mLastUpdate = update;
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt b/packages/SystemUI/tests/utils/src/android/os/LooperKosmos.kt
similarity index 61%
copy from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt
copy to packages/SystemUI/tests/utils/src/android/os/LooperKosmos.kt
index 98a9e93..a8ca9bfc 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt
+++ b/packages/SystemUI/tests/utils/src/android/os/LooperKosmos.kt
@@ -14,16 +14,15 @@
* limitations under the License.
*/
-package com.android.credentialmanager.ui.screens
+package android.os
-import androidx.activity.result.IntentSenderRequest
+import android.testing.TestableLooper
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testCase
-sealed class UiState {
- data object CredentialScreen : UiState()
-
- data class CredentialSelected(
- val intentSenderRequest: IntentSenderRequest?
- ) : UiState()
-
- data object Cancel : UiState()
+val Kosmos.looper by Fixture {
+ checkNotNull(TestableLooper.get(testCase).looper) {
+ "TestableLooper is returning null, make sure the test class is annotated with RunWithLooper"
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt
index 6dd8d07..0660d00 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt
@@ -18,6 +18,7 @@
import com.android.systemui.classifier.FakeClassifierModule
import com.android.systemui.data.FakeSystemUiDataLayerModule
import com.android.systemui.flags.FakeFeatureFlagsClassicModule
+import com.android.systemui.flags.FakeSystemPropertiesHelperModule
import com.android.systemui.log.FakeUiEventLoggerModule
import com.android.systemui.settings.FakeSettingsModule
import com.android.systemui.statusbar.policy.FakeConfigurationControllerModule
@@ -33,6 +34,7 @@
FakeConfigurationControllerModule::class,
FakeExecutorModule::class,
FakeFeatureFlagsClassicModule::class,
+ FakeSystemPropertiesHelperModule::class,
FakeSettingsModule::class,
FakeSplitShadeStateControllerModule::class,
FakeSystemClockModule::class,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
index 3428553..8001604 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
@@ -30,6 +30,7 @@
*
* To use:
* - locally edit your test class to inherit from MemoryTrackingTestCase instead of SysuiTestCase
+ * - Use `atest -d` to prevent files being cleaned up
* - Watch the logcat with tag MEMORY to see the path to the .ahprof file
* - adb pull /path/to/something.ahprof
* - Download ahat from https://sites.google.com/corp/google.com/ahat/home
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
index 69b769e..bc0bf9d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
@@ -27,6 +27,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigModule
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.SystemUIDeviceEntryFaceAuthInteractor
import com.android.systemui.scene.SceneContainerFrameworkModule
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.model.SceneDataSource
@@ -56,6 +59,7 @@
CoroutineTestScopeModule::class,
FakeSystemUiModule::class,
SceneContainerFrameworkModule::class,
+ FaceWakeUpTriggersConfigModule::class,
]
)
interface SysUITestModule {
@@ -69,6 +73,11 @@
@Binds @SysUISingleton fun bindsShadeInteractor(sii: ShadeInteractorImpl): ShadeInteractor
@Binds fun bindSceneDataSource(delegator: SceneDataSourceDelegator): SceneDataSource
+ @Binds
+ fun provideFaceAuthInteractor(
+ sysUIFaceAuthInteractor: SystemUIDeviceEntryFaceAuthInteractor
+ ): DeviceEntryFaceAuthInteractor
+
companion object {
@Provides
fun provideSysuiTestableContext(test: SysuiTestCase): SysuiTestableContext = test.context
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
index 62a1aa9..3d84291 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
@@ -17,6 +17,7 @@
import android.app.ActivityManager
import android.app.admin.DevicePolicyManager
+import android.app.trust.TrustManager
import android.os.UserManager
import android.service.notification.NotificationListenerService
import android.util.DisplayMetrics
@@ -27,6 +28,7 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardViewController
import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
@@ -36,6 +38,7 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.dagger.BiometricLog
import com.android.systemui.log.dagger.BroadcastDispatcherLog
+import com.android.systemui.log.dagger.FaceAuthLog
import com.android.systemui.log.dagger.SceneFrameworkLog
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.model.SysUiState
@@ -65,10 +68,12 @@
import com.android.systemui.statusbar.phone.SystemUIDialogManager
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.statusbar.policy.ZenModeController
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.GlobalSettings
import com.android.wm.shell.bubbles.Bubbles
import dagger.Binds
import dagger.Module
@@ -123,6 +128,10 @@
@get:Provides val deviceEntryIconTransitions: Set<DeviceEntryIconTransition> = emptySet(),
@get:Provides val communalInteractor: CommunalInteractor = mock(),
@get:Provides val sceneLogger: SceneLogger = mock(),
+ @get:Provides val trustManager: TrustManager = mock(),
+ @get:Provides val primaryBouncerInteractor: PrimaryBouncerInteractor = mock(),
+ @get:Provides val keyguardStateController: KeyguardStateController = mock(),
+ @get:Provides val globalSettings: GlobalSettings = mock(),
// log buffers
@get:[Provides BroadcastDispatcherLog]
@@ -131,6 +140,8 @@
val sceneLogBuffer: LogBuffer = mock(),
@get:[Provides BiometricLog]
val biometricLogger: LogBuffer = mock(),
+ @get:[Provides FaceAuthLog]
+ val faceAuthLogger: LogBuffer = mock(),
@get:Provides val lsShadeTransitionLogger: LSShadeTransitionLogger = mock(),
// framework mocks
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt
similarity index 65%
copy from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt
index 98a9e93..ed291d1 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt
@@ -14,16 +14,9 @@
* limitations under the License.
*/
-package com.android.credentialmanager.ui.screens
+package com.android.systemui.animation
-import androidx.activity.result.IntentSenderRequest
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
-sealed class UiState {
- data object CredentialScreen : UiState()
-
- data class CredentialSelected(
- val intentSenderRequest: IntentSenderRequest?
- ) : UiState()
-
- data object Cancel : UiState()
-}
+val Kosmos.dialogTransitionAnimator by Fixture { fakeDialogTransitionAnimator() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt
index 1b951d9..9765d53 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt
@@ -19,12 +19,17 @@
import android.util.Size
import com.android.systemui.biometrics.shared.model.DisplayRotation
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-class FakeDisplayStateRepository : DisplayStateRepository {
+@SysUISingleton
+class FakeDisplayStateRepository @Inject constructor() : DisplayStateRepository {
private val _isInRearDisplayMode = MutableStateFlow<Boolean>(false)
override val isInRearDisplayMode: StateFlow<Boolean> = _isInRearDisplayMode.asStateFlow()
@@ -51,3 +56,8 @@
_currentDisplaySize.value = size
}
}
+
+@Module
+interface FakeDisplayStateRepositoryModule {
+ @Binds fun bindFake(fake: FakeDisplayStateRepository): DisplayStateRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
index 68ef555..8a95136 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
@@ -19,10 +19,15 @@
import android.graphics.Point
import com.android.systemui.biometrics.shared.model.LockoutMode
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
-class FakeFacePropertyRepository : FacePropertyRepository {
+@SysUISingleton
+class FakeFacePropertyRepository @Inject constructor() : FacePropertyRepository {
private val faceSensorInfo = MutableStateFlow<FaceSensorInfo?>(null)
override val sensorInfo: StateFlow<FaceSensorInfo?>
get() = faceSensorInfo
@@ -56,3 +61,8 @@
currentCameraInfo.value = value
}
}
+
+@Module
+interface FakeFacePropertyRepositoryModule {
+ @Binds fun bindFake(fake: FakeFacePropertyRepository): FacePropertyRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
index 005cac4..60d61ac 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
@@ -28,6 +28,7 @@
@SysUISingleton
class FakeFingerprintPropertyRepository @Inject constructor() : FingerprintPropertyRepository {
+ override val propertiesInitialized: MutableStateFlow<Boolean> = MutableStateFlow(false)
private val _sensorId: MutableStateFlow<Int> = MutableStateFlow(-1)
override val sensorId = _sensorId.asStateFlow()
@@ -54,6 +55,7 @@
_strength.value = strength
_sensorType.value = sensorType
_sensorLocations.value = sensorLocations
+ propertiesInitialized.value = true
}
/** setProperties as if the device supports UDFPS_OPTICAL. */
@@ -66,6 +68,16 @@
)
}
+ /** setProperties as if the device supports POWER_BUTTON fingerprint sensor. */
+ fun supportsSideFps() {
+ setProperties(
+ sensorId = 0,
+ strength = SensorStrength.STRONG,
+ sensorType = FingerprintSensorType.POWER_BUTTON,
+ sensorLocations = emptyMap(),
+ )
+ }
+
/** setProperties as if the device supports the rear fingerprint sensor. */
fun supportsRearFps() {
setProperties(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
index c3af437..2e2cf9a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
@@ -78,6 +78,7 @@
val hasCredentialViewShown = kind.value !is PromptKind.Biometric
val showBpForCredential =
Flags.customBiometricPrompt() &&
+ com.android.systemui.Flags.constraintBp() &&
!Utils.isBiometricAllowed(promptInfo) &&
Utils.isDeviceCredentialAllowed(promptInfo) &&
promptInfo.contentView != null
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorKosmos.kt
index e262066..34a9c8a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractorKosmos.kt
@@ -21,9 +21,11 @@
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
val Kosmos.fingerprintPropertyInteractor by Fixture {
FingerprintPropertyInteractor(
+ applicationScope = applicationCoroutineScope,
context = applicationContext,
repository = fingerprintPropertyRepository,
configurationInteractor = configurationInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
index 050c2c9..4d74254c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
@@ -30,7 +30,11 @@
@SysUISingleton
class FakeConfigurationRepository @Inject constructor() : ConfigurationRepository {
- private val _onAnyConfigurationChange = MutableSharedFlow<Unit>()
+ private val _onAnyConfigurationChange =
+ MutableSharedFlow<Unit>(
+ replay = 1,
+ onBufferOverflow = BufferOverflow.DROP_OLDEST,
+ )
override val onAnyConfigurationChange: Flow<Unit> = _onAnyConfigurationChange.asSharedFlow()
private val _onConfigurationChange =
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 6ac702e..8866fd3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -16,6 +16,8 @@
package com.android.systemui.communal.domain.interactor
+import android.os.userManager
+import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.communal.data.repository.communalMediaRepository
import com.android.systemui.communal.data.repository.communalPrefsRepository
import com.android.systemui.communal.data.repository.communalRepository
@@ -29,6 +31,7 @@
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.plugins.activityStarter
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.settings.userTracker
@@ -39,6 +42,7 @@
val Kosmos.communalInteractor by Fixture {
CommunalInteractor(
applicationScope = applicationCoroutineScope,
+ broadcastDispatcher = broadcastDispatcher,
communalRepository = communalRepository,
widgetRepository = communalWidgetRepository,
mediaRepository = communalMediaRepository,
@@ -48,6 +52,8 @@
keyguardInteractor = keyguardInteractor,
editWidgetsActivityStarter = editWidgetsActivityStarter,
userTracker = userTracker,
+ activityStarter = activityStarter,
+ userManager = userManager,
logBuffer = logcatLogBuffer("CommunalInteractor"),
tableLogBuffer = mock(),
communalSettingsInteractor = communalSettingsInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
index 8ff04a63..fbb8ea2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
@@ -15,8 +15,11 @@
package com.android.systemui.deviceentry.data
+import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepositoryModule
+import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepositoryModule
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepositoryModule
import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepositoryModule
+import com.android.systemui.display.data.repository.FakeDisplayRepositoryModule
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepositoryModule
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepositoryModule
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepositoryModule
@@ -30,7 +33,10 @@
FakeDeviceEntryRepositoryModule::class,
FakeDeviceEntryFaceAuthRepositoryModule::class,
FakeDeviceEntryFingerprintAuthRepositoryModule::class,
+ FakeDisplayRepositoryModule::class,
+ FakeDisplayStateRepositoryModule::class,
FakeFingerprintPropertyRepositoryModule::class,
+ FakeFacePropertyRepositoryModule::class,
FakeTrustRepositoryModule::class,
]
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt
index 66c6f86..ebed922 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt
@@ -18,6 +18,7 @@
package com.android.systemui.deviceentry.domain.interactor
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.kosmos.Kosmos
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -25,6 +26,8 @@
val Kosmos.deviceEntryFingerprintAuthInteractor by
Kosmos.Fixture {
DeviceEntryFingerprintAuthInteractor(
+ biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
repository = deviceEntryFingerprintAuthRepository,
+ fingerprintPropertyRepository = fingerprintPropertyRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
index 0d1a31f..e73e295 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
@@ -18,8 +18,8 @@
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
-import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.trustRepository
+import com.android.systemui.flags.fakeSystemPropertiesHelper
+import com.android.systemui.keyguard.domain.interactor.trustInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -34,9 +34,12 @@
repository = deviceEntryRepository,
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
- deviceEntryFaceAuthRepository = deviceEntryFaceAuthRepository,
- trustRepository = trustRepository,
+ faceAuthInteractor = deviceEntryFaceAuthInteractor,
+ trustInteractor = trustInteractor,
flags = sceneContainerFlags,
deviceUnlockedInteractor = deviceUnlockedInteractor,
+ fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
+ biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
+ systemPropertiesHelper = fakeSystemPropertiesHelper,
)
}
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
index b04161a..81123d0 100644
--- 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
@@ -18,7 +18,7 @@
package com.android.systemui.deviceentry.domain.interactor
-import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.fingerprintPropertyInteractor
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.kosmos.Kosmos
@@ -27,7 +27,7 @@
val Kosmos.deviceEntryUdfpsInteractor by Fixture {
DeviceEntryUdfpsInteractor(
- fingerprintPropertyRepository = fingerprintPropertyRepository,
+ fingerprintPropertyInteractor = fingerprintPropertyInteractor,
fingerprintAuthRepository = deviceEntryFingerprintAuthRepository,
biometricSettingsRepository = biometricSettingsRepository,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index d8098b7..0fc0a3c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -16,7 +16,11 @@
package com.android.systemui.display.data.repository
import android.view.Display
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.util.mockito.mock
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import org.mockito.Mockito.`when` as whenever
@@ -42,8 +46,9 @@
fun createPendingDisplay(id: Int = 0): DisplayRepository.PendingDisplay =
mock<DisplayRepository.PendingDisplay> { whenever(this.id).thenReturn(id) }
+@SysUISingleton
/** Fake [DisplayRepository] implementation for testing. */
-class FakeDisplayRepository : DisplayRepository {
+class FakeDisplayRepository @Inject constructor() : DisplayRepository {
private val flow = MutableSharedFlow<Set<Display>>(replay = 1)
private val pendingDisplayFlow =
MutableSharedFlow<DisplayRepository.PendingDisplay?>(replay = 1)
@@ -71,3 +76,8 @@
override val displayChangeEvent: Flow<Int> = _displayChangeEvent
suspend fun emitDisplayChangeEvent(displayId: Int) = _displayChangeEvent.emit(displayId)
}
+
+@Module
+interface FakeDisplayRepositoryModule {
+ @Binds fun bindFake(fake: FakeDisplayRepository): DisplayRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeSystemPropertiesHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeSystemPropertiesHelper.kt
new file mode 100644
index 0000000..2f30d34
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeSystemPropertiesHelper.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+
+@SysUISingleton
+class FakeSystemPropertiesHelper @Inject constructor() : SystemPropertiesHelper() {
+ private val fakeProperties = mutableMapOf<String, Any>()
+
+ override fun get(name: String): String {
+ return fakeProperties[name] as String
+ }
+
+ override fun get(name: String, def: String?): String {
+ return checkNotNull(fakeProperties[name] as String? ?: def)
+ }
+
+ override fun getBoolean(name: String, default: Boolean): Boolean {
+ return fakeProperties[name] as Boolean? ?: default
+ }
+
+ override fun setBoolean(name: String, value: Boolean) {
+ fakeProperties[name] = value
+ }
+
+ override fun set(name: String, value: String) {
+ fakeProperties[name] = value
+ }
+
+ override fun set(name: String, value: Int) {
+ fakeProperties[name] = value
+ }
+
+ override fun erase(name: String) {
+ fakeProperties.remove(name)
+ }
+}
+
+@Module
+interface FakeSystemPropertiesHelperModule {
+ @Binds fun bindFake(fake: FakeSystemPropertiesHelper): SystemPropertiesHelper
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
index 365d97f..d6f2f77 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
@@ -62,6 +62,7 @@
}
val Kosmos.systemPropertiesHelper by Kosmos.Fixture { SystemPropertiesHelper() }
+val Kosmos.fakeSystemPropertiesHelper by Kosmos.Fixture { FakeSystemPropertiesHelper() }
var Kosmos.serverFlagReader: ServerFlagReader by Kosmos.Fixture { serverFlagReaderFake }
val Kosmos.serverFlagReaderFake by Kosmos.Fixture { ServerFlagReaderFake() }
var Kosmos.restarter: Restarter by Kosmos.Fixture { mock() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
index 592fa38..5b642ea 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardClockRepository.kt
@@ -42,7 +42,7 @@
private val _currentClockId = MutableStateFlow(DEFAULT_CLOCK_ID)
override val currentClockId: Flow<ClockId> = _currentClockId
- private val _currentClock = MutableStateFlow(null)
+ private val _currentClock: MutableStateFlow<ClockController?> = MutableStateFlow(null)
override val currentClock = _currentClock
private val _previewClockPair =
@@ -60,6 +60,10 @@
override fun setClockSize(@ClockSize size: Int) {
_clockSize.value = size
}
+
+ fun setCurrentClock(clockController: ClockController) {
+ _currentClock.value = clockController
+ }
}
@Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 793e2d7..1e305d6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -18,7 +18,6 @@
package com.android.systemui.keyguard.data.repository
import android.graphics.Point
-import com.android.systemui.common.shared.model.Position
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
@@ -58,9 +57,6 @@
private val _bottomAreaAlpha = MutableStateFlow(1f)
override val bottomAreaAlpha: StateFlow<Float> = _bottomAreaAlpha
- private val _clockPosition = MutableStateFlow(Position(0, 0))
- override val clockPosition: StateFlow<Position> = _clockPosition
-
private val _isKeyguardShowing = MutableStateFlow(false)
override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
@@ -149,10 +145,6 @@
_bottomAreaAlpha.value = alpha
}
- override fun setClockPosition(x: Int, y: Int) {
- _clockPosition.value = Position(x, y)
- }
-
fun setKeyguardShowing(isShowing: Boolean) {
_isKeyguardShowing.value = isShowing
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt
index b24b95e..f26bb83 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt
@@ -35,7 +35,10 @@
private val _revealAmount: MutableStateFlow<Float> = MutableStateFlow(0.0f)
override val revealAmount: Flow<Float> = _revealAmount
- override fun startRevealAmountAnimator(reveal: Boolean) {
+ override val isAnimating: Boolean
+ get() = false
+
+ override fun startRevealAmountAnimator(reveal: Boolean, duration: Long) {
if (reveal) {
_revealAmount.value = 1.0f
} else {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
index 8452963..75489b6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
@@ -17,10 +17,12 @@
package com.android.systemui.keyguard.data.repository
import android.os.fakeExecutorHandler
-import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.WEATHER_CLOCK_BLUEPRINT_ID
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT
+import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.util.ThreadAssert
import com.android.systemui.util.mockito.mock
@@ -28,8 +30,13 @@
val Kosmos.keyguardBlueprintRepository by
Kosmos.Fixture {
KeyguardBlueprintRepository(
- configurationRepository = configurationRepository,
- blueprints = setOf(defaultBlueprint),
+ blueprints =
+ setOf(
+ defaultBlueprint,
+ splitShadeBlueprint,
+ weatherClockBlueprint,
+ splitShadeWeatherClockBlueprint,
+ ),
handler = fakeExecutorHandler,
assert = mock<ThreadAssert>(),
)
@@ -42,3 +49,27 @@
override val sections: List<KeyguardSection>
get() = listOf()
}
+
+private val weatherClockBlueprint =
+ object : KeyguardBlueprint {
+ override val id: String
+ get() = WEATHER_CLOCK_BLUEPRINT_ID
+ override val sections: List<KeyguardSection>
+ get() = listOf()
+ }
+
+private val splitShadeWeatherClockBlueprint =
+ object : KeyguardBlueprint {
+ override val id: String
+ get() = SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID
+ override val sections: List<KeyguardSection>
+ get() = listOf()
+ }
+
+private val splitShadeBlueprint =
+ object : KeyguardBlueprint {
+ override val id: String
+ get() = SplitShadeKeyguardBlueprint.Companion.ID
+ override val sections: List<KeyguardSection>
+ get() = listOf()
+ }
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
index a9d89a3..40131c7 100644
--- 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
@@ -19,7 +19,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.content.applicationContext
-import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.doze.util.burnInHelperWrapper
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -31,7 +31,7 @@
context = applicationContext,
burnInHelperWrapper = burnInHelperWrapper,
scope = applicationCoroutineScope,
- configurationRepository = configurationRepository,
+ configurationInteractor = configurationInteractor,
keyguardInteractor = keyguardInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt
index 5dd5073..a45b269 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitionsKosmos.kt
@@ -19,12 +19,10 @@
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testDispatcher
val Kosmos.glanceableHubTransitions by
Kosmos.Fixture {
GlanceableHubTransitions(
- bgDispatcher = testDispatcher,
transitionRepository = keyguardTransitionRepository,
transitionInteractor = keyguardTransitionInteractor,
communalInteractor = communalInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt
index 8b0bba1..87d6c17 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt
@@ -17,6 +17,8 @@
package com.android.systemui.keyguard.domain.interactor
import android.content.applicationContext
+import com.android.systemui.biometrics.domain.interactor.fingerprintPropertyInteractor
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.keyguard.data.repository.keyguardBlueprintRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
@@ -30,5 +32,7 @@
context = applicationContext,
splitShadeStateController = splitShadeStateController,
clockInteractor = keyguardClockInteractor,
+ configurationInteractor = configurationInteractor,
+ fingerprintPropertyInteractor = fingerprintPropertyInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt
index 58e0a3b..d5bdbdb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt
@@ -29,6 +29,6 @@
lightRevealScrimRepository,
applicationCoroutineScope,
scrimLogger,
- powerInteractor,
+ { powerInteractor },
)
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/TrustInteractorKosmos.kt
similarity index 65%
copy from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/TrustInteractorKosmos.kt
index 98a9e93..0ebf164 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/TrustInteractorKosmos.kt
@@ -14,16 +14,10 @@
* limitations under the License.
*/
-package com.android.credentialmanager.ui.screens
+package com.android.systemui.keyguard.domain.interactor
-import androidx.activity.result.IntentSenderRequest
+import com.android.systemui.keyguard.data.repository.trustRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
-sealed class UiState {
- data object CredentialScreen : UiState()
-
- data class CredentialSelected(
- val intentSenderRequest: IntentSenderRequest?
- ) : UiState()
-
- data object Cancel : UiState()
-}
+val Kosmos.trustInteractor by Fixture { TrustInteractor(repository = trustRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelKosmos.kt
new file mode 100644
index 0000000..b7d9676
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModelKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.bouncer.domain.interactor.alternateBouncerInteractor
+import com.android.systemui.deviceentry.domain.interactor.biometricMessageInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.time.systemClock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+val Kosmos.alternateBouncerMessageAreaViewModel by
+ Kosmos.Fixture {
+ AlternateBouncerMessageAreaViewModel(
+ biometricMessageInteractor = biometricMessageInteractor,
+ alternateBouncerInteractor = alternateBouncerInteractor,
+ systemClock = systemClock,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelKosmos.kt
index 00741eb..298c70d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.keyguard.domain.interactor.fromDreamingTransitionInteractor
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
@@ -25,5 +26,6 @@
DreamingToGlanceableHubTransitionViewModel(
configurationInteractor = configurationInteractor,
animationFlow = keyguardTransitionAnimationFlow,
+ fromDreamingTransitionInteractor = fromDreamingTransitionInteractor
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt
index 5f70a2f..450dcc2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt
@@ -16,7 +16,6 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -26,7 +25,6 @@
@ExperimentalCoroutinesApi
val Kosmos.dreamingToLockscreenTransitionViewModel by Fixture {
DreamingToLockscreenTransitionViewModel(
- keyguardTransitionInteractor = keyguardTransitionInteractor,
fromDreamingTransitionInteractor = mock(),
animationFlow = keyguardTransitionAnimationFlow,
)
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
index 75e3ac2..a863edf 100644
--- 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
@@ -23,6 +23,7 @@
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.notificationsKeyguardInteractor
import com.android.systemui.statusbar.phone.dozeParameters
@@ -31,6 +32,7 @@
val Kosmos.keyguardRootViewModel by Fixture {
KeyguardRootViewModel(
+ scope = applicationCoroutineScope,
deviceEntryInteractor = deviceEntryInteractor,
dozeParameters = dozeParameters,
keyguardInteractor = keyguardInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
index 0b13858..b34681a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
@@ -2,6 +2,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@@ -9,3 +10,7 @@
var Kosmos.testScope by Fixture { TestScope(testDispatcher) }
var Kosmos.applicationCoroutineScope by Fixture { testScope.backgroundScope }
var Kosmos.testCase: SysuiTestCase by Fixture()
+var Kosmos.backgroundCoroutineContext: CoroutineContext by Fixture {
+ testScope.backgroundScope.coroutineContext
+}
+var Kosmos.mainCoroutineContext: CoroutineContext by Fixture { testScope.coroutineContext }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/data/repository/FakeSpatializerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/data/repository/FakeSpatializerRepository.kt
index 0183b97..63e2d4b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/data/repository/FakeSpatializerRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/data/repository/FakeSpatializerRepository.kt
@@ -18,23 +18,27 @@
import android.media.AudioDeviceAttributes
import com.android.settingslib.media.data.repository.SpatializerRepository
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
class FakeSpatializerRepository : SpatializerRepository {
var defaultSpatialAudioAvailable: Boolean = false
+ var defaultHeadTrackingAvailable: Boolean = false
private val spatialAudioAvailabilityByDevice: MutableMap<AudioDeviceAttributes, Boolean> =
mutableMapOf()
+ private val headTrackingAvailabilityByDevice: MutableMap<AudioDeviceAttributes, Boolean> =
+ mutableMapOf()
private val spatialAudioCompatibleDevices: MutableList<AudioDeviceAttributes> = mutableListOf()
- private val mutableHeadTrackingAvailable = MutableStateFlow(false)
private val headTrackingEnabledByDevice = mutableMapOf<AudioDeviceAttributes, Boolean>()
- override val isHeadTrackingAvailable: StateFlow<Boolean> =
- mutableHeadTrackingAvailable.asStateFlow()
+ override suspend fun isHeadTrackingAvailableForDevice(
+ audioDeviceAttributes: AudioDeviceAttributes
+ ): Boolean =
+ headTrackingAvailabilityByDevice.getOrDefault(
+ audioDeviceAttributes,
+ defaultHeadTrackingAvailable
+ )
override suspend fun isSpatialAudioAvailableForDevice(
audioDeviceAttributes: AudioDeviceAttributes
@@ -77,7 +81,10 @@
spatialAudioAvailabilityByDevice[audioDeviceAttributes] = isAvailable
}
- fun setIsHeadTrackingAvailable(isAvailable: Boolean) {
- mutableHeadTrackingAvailable.value = isAvailable
+ fun setIsHeadTrackingAvailable(
+ audioDeviceAttributes: AudioDeviceAttributes,
+ isAvailable: Boolean,
+ ) {
+ headTrackingAvailabilityByDevice[audioDeviceAttributes] = isAvailable
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
index 23d657d..1ce2610 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
@@ -16,16 +16,86 @@
package com.android.systemui.qs
+import android.app.admin.devicePolicyManager
+import android.content.applicationContext
+import android.os.fakeExecutorHandler
+import android.os.looper
+import com.android.internal.logging.metricsLogger
+import com.android.internal.logging.uiEventLogger
import com.android.internal.logging.uiEventLoggerFake
import com.android.systemui.InstanceIdSequenceFake
+import com.android.systemui.animation.dialogTransitionAnimator
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.classifier.falsingManager
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.plugins.activityStarter
import com.android.systemui.plugins.qs.QSFactory
+import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractorImpl
+import com.android.systemui.qs.footer.foregroundServicesRepository
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.tiles.di.NewQSTileFactory
+import com.android.systemui.security.data.repository.securityRepository
+import com.android.systemui.settings.userTracker
+import com.android.systemui.statusbar.policy.deviceProvisionedController
+import com.android.systemui.statusbar.policy.securityController
+import com.android.systemui.user.data.repository.userSwitcherRepository
+import com.android.systemui.user.domain.interactor.userSwitcherInteractor
+import com.android.systemui.util.mockito.mock
-val Kosmos.instanceIdSequenceFake: InstanceIdSequenceFake by
- Kosmos.Fixture { InstanceIdSequenceFake(0) }
-val Kosmos.qsEventLogger: QsEventLoggerFake by
- Kosmos.Fixture { QsEventLoggerFake(uiEventLoggerFake, instanceIdSequenceFake) }
+val Kosmos.instanceIdSequenceFake: InstanceIdSequenceFake by Fixture { InstanceIdSequenceFake(0) }
+val Kosmos.qsEventLogger: QsEventLoggerFake by Fixture {
+ QsEventLoggerFake(uiEventLoggerFake, instanceIdSequenceFake)
+}
-var Kosmos.newQSTileFactory by Kosmos.Fixture<NewQSTileFactory>()
-var Kosmos.qsTileFactory by Kosmos.Fixture<QSFactory>()
+var Kosmos.newQSTileFactory by Fixture<NewQSTileFactory>()
+var Kosmos.qsTileFactory by Fixture<QSFactory>()
+
+val Kosmos.fgsManagerController by Fixture { FakeFgsManagerController() }
+
+val Kosmos.footerActionsController by Fixture {
+ FooterActionsController(
+ fgsManagerController = fgsManagerController,
+ )
+}
+
+val Kosmos.qsSecurityFooterUtils by Fixture {
+ QSSecurityFooterUtils(
+ applicationContext,
+ devicePolicyManager,
+ userTracker,
+ fakeExecutorHandler,
+ activityStarter,
+ securityController,
+ looper,
+ dialogTransitionAnimator,
+ )
+}
+
+val Kosmos.footerActionsInteractor by Fixture {
+ FooterActionsInteractorImpl(
+ activityStarter = activityStarter,
+ metricsLogger = metricsLogger,
+ uiEventLogger = uiEventLogger,
+ deviceProvisionedController = deviceProvisionedController,
+ qsSecurityFooterUtils = qsSecurityFooterUtils,
+ fgsManagerController = fgsManagerController,
+ userSwitcherInteractor = userSwitcherInteractor,
+ securityRepository = securityRepository,
+ foregroundServicesRepository = foregroundServicesRepository,
+ userSwitcherRepository = userSwitcherRepository,
+ broadcastDispatcher = broadcastDispatcher,
+ bgDispatcher = testDispatcher,
+ )
+}
+
+val Kosmos.footerActionsViewModelFactory by Fixture {
+ FooterActionsViewModel.Factory(
+ context = applicationContext,
+ falsingManager = falsingManager,
+ footerActionsInteractor = footerActionsInteractor,
+ globalActionsDialogLiteProvider = { mock() },
+ showPowerButton = true,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/PanelExpansionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/ForegroundServicesRepositoryKosmos.kt
similarity index 61%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/PanelExpansionInteractorKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/ForegroundServicesRepositoryKosmos.kt
index a025846..8f81b5e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/PanelExpansionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/ForegroundServicesRepositoryKosmos.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+package com.android.systemui.qs.footer
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.scene.domain.interactor.PanelExpansionInteractor
-import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.qs.fgsManagerController
+import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepositoryImpl
-val Kosmos.panelExpansionInteractor by Fixture {
- PanelExpansionInteractor(
- sceneInteractor = sceneInteractor,
- shadeRepository = shadeRepository,
+val Kosmos.foregroundServicesRepository by Fixture {
+ ForegroundServicesRepositoryImpl(
+ fgsManagerController = fgsManagerController,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/PanelExpansionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/security/data/repository/SecurityRepositoryKosmos.kt
similarity index 61%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/PanelExpansionInteractorKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/security/data/repository/SecurityRepositoryKosmos.kt
index a025846..6ac5bcb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/PanelExpansionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/security/data/repository/SecurityRepositoryKosmos.kt
@@ -14,17 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+package com.android.systemui.security.data.repository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.scene.domain.interactor.PanelExpansionInteractor
-import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.statusbar.policy.securityController
-val Kosmos.panelExpansionInteractor by Fixture {
- PanelExpansionInteractor(
- sceneInteractor = sceneInteractor,
- shadeRepository = shadeRepository,
+val Kosmos.securityRepository by Fixture {
+ SecurityRepositoryImpl(
+ securityController = securityController,
+ bgDispatcher = testDispatcher,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
index 4aab822..728c67a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
@@ -18,11 +18,13 @@
package com.android.systemui.shade.data.repository
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.shared.model.ShadeMode
import dagger.Binds
import dagger.Module
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
/** Fake implementation of [ShadeRepository] */
@SysUISingleton
@@ -59,6 +61,9 @@
override val legacyLockscreenShadeTracking = MutableStateFlow(false)
+ private val _shadeMode = MutableStateFlow<ShadeMode>(ShadeMode.Single)
+ override val shadeMode: StateFlow<ShadeMode> = _shadeMode.asStateFlow()
+
@Deprecated("Use ShadeInteractor instead")
override fun setLegacyIsQsExpanded(legacyIsQsExpanded: Boolean) {
_legacyIsQsExpanded.value = legacyIsQsExpanded
@@ -131,6 +136,10 @@
override fun setLegacyShadeExpansion(expandedFraction: Float) {
_legacyShadeExpansion.value = expandedFraction
}
+
+ override fun setShadeMode(shadeMode: ShadeMode) {
+ _shadeMode.value = shadeMode
+ }
}
@Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/PanelExpansionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorKosmos.kt
similarity index 67%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/PanelExpansionInteractorKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorKosmos.kt
index a025846..09c8f87 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/PanelExpansionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorKosmos.kt
@@ -14,17 +14,17 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+package com.android.systemui.shade.domain.interactor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.scene.domain.interactor.PanelExpansionInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.shade.data.repository.shadeRepository
-val Kosmos.panelExpansionInteractor by Fixture {
- PanelExpansionInteractor(
- sceneInteractor = sceneInteractor,
- shadeRepository = shadeRepository,
+val Kosmos.panelExpansionInteractor by Fixture { panelExpansionInteractorImpl }
+val Kosmos.panelExpansionInteractorImpl by Fixture {
+ PanelExpansionInteractorImpl(
+ sceneInteractor,
+ shadeInteractor,
+ shadeAnimationInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt
index d2dd200..6d24e2a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade.domain.interactor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.data.repository.shadeAnimationRepository
@@ -24,5 +25,9 @@
Kosmos.Fixture { ShadeAnimationInteractorEmptyImpl(shadeAnimationRepository) }
var Kosmos.shadeAnimationInteractorSceneContainerImpl: ShadeAnimationInteractorSceneContainerImpl by
Kosmos.Fixture {
- ShadeAnimationInteractorSceneContainerImpl(shadeAnimationRepository, sceneInteractor)
+ ShadeAnimationInteractorSceneContainerImpl(
+ testScope.backgroundScope,
+ shadeAnimationRepository,
+ sceneInteractor
+ )
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
index 2bd76be..07e2d6b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
@@ -47,6 +47,7 @@
scope = applicationCoroutineScope,
sceneInteractor = sceneInteractor,
sharedNotificationContainerInteractor = sharedNotificationContainerInteractor,
+ shadeRepository = shadeRepository,
)
}
val Kosmos.shadeInteractorLegacyImpl by
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt
new file mode 100644
index 0000000..b99fdb9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.domain.startable
+
+import android.content.applicationContext
+import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.statusbar.policy.splitShadeStateController
+
+val Kosmos.shadeStartable by Fixture {
+ ShadeStartable(
+ applicationScope = applicationCoroutineScope,
+ applicationContext = applicationContext,
+ configurationRepository = configurationRepository,
+ shadeRepository = shadeRepository,
+ controller = splitShadeStateController,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt
index d3a8e0c..0950f04 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt
@@ -18,7 +18,19 @@
import android.content.pm.launcherApps
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.backgroundCoroutineContext
+import com.android.systemui.kosmos.mainCoroutineContext
import com.android.systemui.statusbar.notification.collection.notifcollection.commonNotifCollection
val Kosmos.iconManager by
- Kosmos.Fixture { IconManager(commonNotifCollection, launcherApps, iconBuilder) }
+ Kosmos.Fixture {
+ IconManager(
+ commonNotifCollection,
+ launcherApps,
+ iconBuilder,
+ applicationCoroutineScope,
+ backgroundCoroutineContext,
+ mainCoroutineContext,
+ )
+ }
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
index c013664..de0cc65 100644
--- 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
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
-import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.dump.dumpManager
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
@@ -56,7 +55,6 @@
keyguardInteractor = keyguardInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
shadeInteractor = shadeInteractor,
- communalInteractor = communalInteractor,
alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/SecurityControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/SecurityControllerKosmos.kt
new file mode 100644
index 0000000..67a5cc9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/SecurityControllerKosmos.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.content.applicationContext
+import android.os.fakeExecutorHandler
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.settings.userTracker
+
+val Kosmos.securityController by Fixture {
+ SecurityControllerImpl(
+ applicationContext,
+ userTracker,
+ fakeExecutorHandler,
+ broadcastDispatcher,
+ fakeExecutor,
+ fakeExecutor,
+ dumpManager,
+ )
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/FakeUserSwitcherRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserSwitcherRepository.kt
similarity index 94%
rename from packages/SystemUI/tests/src/com/android/systemui/user/data/repository/FakeUserSwitcherRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserSwitcherRepository.kt
index 758fe93a..90fb60b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/FakeUserSwitcherRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserSwitcherRepository.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/UserSwitcherRepositoryKosmos.kt
similarity index 65%
copy from packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/UserSwitcherRepositoryKosmos.kt
index 98a9e93..1519f30 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/UiState.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/UserSwitcherRepositoryKosmos.kt
@@ -14,16 +14,9 @@
* limitations under the License.
*/
-package com.android.credentialmanager.ui.screens
+package com.android.systemui.user.data.repository
-import androidx.activity.result.IntentSenderRequest
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
-sealed class UiState {
- data object CredentialScreen : UiState()
-
- data class CredentialSelected(
- val intentSenderRequest: IntentSenderRequest?
- ) : UiState()
-
- data object Cancel : UiState()
-}
+val Kosmos.userSwitcherRepository by Fixture { FakeUserSwitcherRepository() }
diff --git a/packages/Tethering/OWNERS b/packages/Tethering/OWNERS
deleted file mode 100644
index aa87958..0000000
--- a/packages/Tethering/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/core/java/com/android/server/net/OWNERS
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index e0fe88a..004f37c 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -59,6 +59,8 @@
import android.hardware.camera2.extension.SizeList;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
+import android.hardware.camera2.params.ColorSpaceProfiles;
+import android.hardware.camera2.params.DynamicRangeProfiles;
import android.hardware.camera2.utils.SurfaceUtils;
import android.media.Image;
import android.media.ImageReader;
@@ -526,7 +528,7 @@
*/
public static Pair<PreviewExtenderImpl, ImageCaptureExtenderImpl> initializeExtension(
int extensionType) {
- if (Flags.concertMode()) {
+ if (Flags.concertModeApi()) {
if (extensionType == CameraExtensionCharacteristics.EXTENSION_EYES_FREE_VIDEOGRAPHY) {
// Basic extensions are deprecated starting with extension version 1.5
return new Pair<>(new PreviewExtenderImpl() {
@@ -711,7 +713,7 @@
* @hide
*/
public static AdvancedExtenderImpl initializeAdvancedExtensionImpl(int extensionType) {
- if (Flags.concertMode()) {
+ if (Flags.concertModeApi()) {
if (extensionType == CameraExtensionCharacteristics.EXTENSION_EYES_FREE_VIDEOGRAPHY) {
if (EFV_SUPPORTED) {
return new EyesFreeVideographyAdvancedExtenderImpl();
@@ -1228,7 +1230,6 @@
return null;
}
-
}
private class CaptureCallbackStub implements SessionProcessorImpl.CaptureCallback {
@@ -1566,6 +1567,10 @@
private String mCameraId = null;
private IBinder mToken;
+ OutputSurfaceImplStub mOutputPreviewSurfaceImpl;
+ OutputSurfaceImplStub mOutputImageCaptureSurfaceImpl;
+ OutputSurfaceImplStub mOutputPostviewSurfaceImpl;
+
public SessionProcessorImplStub(SessionProcessorImpl sessionProcessor) {
mSessionProcessor = sessionProcessor;
}
@@ -1574,21 +1579,20 @@
public CameraSessionConfig initSession(IBinder token, String cameraId,
Map<String, CameraMetadataNative> charsMapNative, OutputSurface previewSurface,
OutputSurface imageCaptureSurface, OutputSurface postviewSurface) {
- OutputSurfaceImplStub outputPreviewSurfaceImpl =
- new OutputSurfaceImplStub(previewSurface);
- OutputSurfaceImplStub outputImageCaptureSurfaceImpl =
- new OutputSurfaceImplStub(imageCaptureSurface);
- OutputSurfaceImplStub outputPostviewSurfaceImpl =
- new OutputSurfaceImplStub(postviewSurface);
+ mOutputPreviewSurfaceImpl = new OutputSurfaceImplStub(previewSurface);
+ mOutputImageCaptureSurfaceImpl = new OutputSurfaceImplStub(imageCaptureSurface);
+ mOutputPostviewSurfaceImpl = new OutputSurfaceImplStub(postviewSurface);
Camera2SessionConfigImpl sessionConfig;
if (LATENCY_IMPROVEMENTS_SUPPORTED) {
+ int outputsColorSpace = getColorSpaceFromOutputSurfaces(previewSurface,
+ imageCaptureSurface, postviewSurface);
OutputSurfaceConfigurationImplStub outputSurfaceConfigs =
- new OutputSurfaceConfigurationImplStub(outputPreviewSurfaceImpl,
+ new OutputSurfaceConfigurationImplStub(mOutputPreviewSurfaceImpl,
// Image Analysis Output is currently only supported in CameraX
- outputImageCaptureSurfaceImpl, null /*imageAnalysisSurfaceConfig*/,
- outputPostviewSurfaceImpl);
+ mOutputImageCaptureSurfaceImpl, null /*imageAnalysisSurfaceConfig*/,
+ mOutputPostviewSurfaceImpl, outputsColorSpace);
sessionConfig = mSessionProcessor.initSession(cameraId,
getCharacteristicsMap(charsMapNative),
@@ -1596,8 +1600,8 @@
} else {
sessionConfig = mSessionProcessor.initSession(cameraId,
getCharacteristicsMap(charsMapNative),
- getApplicationContext(), outputPreviewSurfaceImpl,
- outputImageCaptureSurfaceImpl, null /*imageAnalysisSurfaceConfig*/);
+ getApplicationContext(), mOutputPreviewSurfaceImpl,
+ mOutputImageCaptureSurfaceImpl, null /*imageAnalysisSurfaceConfig*/);
}
List<Camera2OutputConfigImpl> outputConfigs = sessionConfig.getOutputConfigs();
@@ -1615,6 +1619,11 @@
}
ret.outputConfigs.add(entry);
}
+ if (Flags.extension10Bit() && EFV_SUPPORTED) {
+ ret.colorSpace = sessionConfig.getColorSpace();
+ } else {
+ ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
+ }
ret.sessionTemplateId = sessionConfig.getSessionTemplateId();
ret.sessionType = -1;
if (LATENCY_IMPROVEMENTS_SUPPORTED) {
@@ -1632,6 +1641,18 @@
public void deInitSession(IBinder token) {
CameraExtensionsProxyService.unregisterDeathRecipient(mToken, this);
mSessionProcessor.deInitSession();
+
+ if (Flags.surfaceLeakFix()) {
+ if (mOutputImageCaptureSurfaceImpl.mSurface != null) {
+ mOutputImageCaptureSurfaceImpl.mSurface.release();
+ }
+ if (mOutputPreviewSurfaceImpl.mSurface != null) {
+ mOutputPreviewSurfaceImpl.mSurface.release();
+ }
+ if (mOutputPostviewSurfaceImpl.mSurface != null) {
+ mOutputPostviewSurfaceImpl.mSurface.release();
+ }
+ }
}
@Override
@@ -1707,6 +1728,24 @@
public void binderDied() {
mSessionProcessor.deInitSession();
}
+
+ // Get the color space of the output configurations. All of the OutputSurfaces
+ // can be assumed to have the same color space so return the color space
+ // of any non-null OutputSurface
+ private int getColorSpaceFromOutputSurfaces(OutputSurface previewSurface,
+ OutputSurface imageCaptureSurface, OutputSurface postviewSurface) {
+ int colorSpace = ColorSpaceProfiles.UNSPECIFIED;
+
+ if (previewSurface.surface != null) {
+ colorSpace = previewSurface.colorSpace;
+ } else if (imageCaptureSurface.surface != null) {
+ colorSpace = imageCaptureSurface.colorSpace;
+ } else if (postviewSurface.surface != null) {
+ colorSpace = postviewSurface.colorSpace;
+ }
+
+ return colorSpace;
+ }
}
private class OutputSurfaceConfigurationImplStub implements OutputSurfaceConfigurationImpl {
@@ -1714,6 +1753,17 @@
private OutputSurfaceImpl mOutputImageCaptureSurfaceImpl;
private OutputSurfaceImpl mOutputImageAnalysisSurfaceImpl;
private OutputSurfaceImpl mOutputPostviewSurfaceImpl;
+ private int mColorSpace;
+
+ public OutputSurfaceConfigurationImplStub(OutputSurfaceImpl previewOutput,
+ OutputSurfaceImpl imageCaptureOutput, OutputSurfaceImpl imageAnalysisOutput,
+ OutputSurfaceImpl postviewOutput, int colorSpace) {
+ mOutputPreviewSurfaceImpl = previewOutput;
+ mOutputImageCaptureSurfaceImpl = imageCaptureOutput;
+ mOutputImageAnalysisSurfaceImpl = imageAnalysisOutput;
+ mOutputPostviewSurfaceImpl = postviewOutput;
+ mColorSpace = colorSpace;
+ }
public OutputSurfaceConfigurationImplStub(OutputSurfaceImpl previewOutput,
OutputSurfaceImpl imageCaptureOutput, OutputSurfaceImpl imageAnalysisOutput,
@@ -1722,6 +1772,7 @@
mOutputImageCaptureSurfaceImpl = imageCaptureOutput;
mOutputImageAnalysisSurfaceImpl = imageAnalysisOutput;
mOutputPostviewSurfaceImpl = postviewOutput;
+ mColorSpace = ColorSpaceProfiles.UNSPECIFIED;
}
@Override
@@ -1743,6 +1794,11 @@
public OutputSurfaceImpl getPostviewOutputSurface() {
return mOutputPostviewSurfaceImpl;
}
+
+ @Override
+ public int getColorSpace() {
+ return mColorSpace;
+ }
}
private class OutputSurfaceImplStub implements OutputSurfaceImpl {
@@ -1751,11 +1807,10 @@
private final int mImageFormat;
private final int mDataspace;
private final long mUsage;
+ private final long mDynamicRangeProfile;
public OutputSurfaceImplStub(OutputSurface outputSurface) {
mSurface = outputSurface.surface;
- mSize = new Size(outputSurface.size.width, outputSurface.size.height);
- mImageFormat = outputSurface.imageFormat;
if (mSurface != null) {
mDataspace = SurfaceUtils.getSurfaceDataspace(mSurface);
mUsage = SurfaceUtils.getSurfaceUsage(mSurface);
@@ -1763,6 +1818,9 @@
mDataspace = -1;
mUsage = 0;
}
+ mDynamicRangeProfile = outputSurface.dynamicRangeProfile;
+ mSize = new Size(outputSurface.size.width, outputSurface.size.height);
+ mImageFormat = outputSurface.imageFormat;
}
@Override
@@ -1789,6 +1847,12 @@
public long getUsage() {
return mUsage;
}
+
+ @Override
+ public long getDynamicRangeProfile() {
+ return mDynamicRangeProfile;
+ }
+
}
private class PreviewExtenderImplStub extends IPreviewExtenderImpl.Stub implements
@@ -2518,6 +2582,11 @@
private static CameraOutputConfig getCameraOutputConfig(Camera2OutputConfigImpl output) {
CameraOutputConfig ret = new CameraOutputConfig();
+ if (Flags.extension10Bit() && EFV_SUPPORTED) {
+ ret.dynamicRangeProfile = output.getDynamicRangeProfile();
+ } else {
+ ret.dynamicRangeProfile = DynamicRangeProfiles.STANDARD;
+ }
ret.outputId = new OutputConfigId();
ret.outputId.id = output.getId();
ret.physicalCameraId = output.getPhysicalCameraId();
diff --git a/ravenwood/README.md b/ravenwood/README.md
index 9c4fda7..8cafb43 100644
--- a/ravenwood/README.md
+++ b/ravenwood/README.md
@@ -1,9 +1,11 @@
# Ravenwood
-Ravenwood is an officially-supported lightweight unit testing environment for Android platform code that runs on the host.
+Ravenwood is a lightweight unit testing environment for Android platform code that runs on the host.
Ravenwood’s focus on Android platform use-cases, improved maintainability, and device consistency distinguishes it from Robolectric, which remains a popular choice for app testing.
+> **Note:** Active development of Ravenwood has been paused as of March 2024. Existing Ravenwood tests will continue running, but support has moved to a self-service model.
+
## Background
Executing tests on a typical Android device has substantial overhead, such as flashing the build, waiting for the boot to complete, and retrying tests that fail due to general flakiness.
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index af47ed2..73584154 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -611,12 +611,12 @@
if (svcConnTracingEnabled()) {
logTraceSvcConn("getWindow", "windowId=" + windowId);
}
+ int displayId = Display.INVALID_DISPLAY;
+ if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
+ displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowId(
+ mSystemSupport.getCurrentUserIdLocked(), windowId);
+ }
synchronized (mLock) {
- int displayId = Display.INVALID_DISPLAY;
- if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
- displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowIdLocked(
- mSystemSupport.getCurrentUserIdLocked(), windowId);
- }
ensureWindowsAvailableTimedLocked(displayId);
if (!hasRightsToCurrentUserLocked()) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index cbb66dc..4be303a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -40,6 +40,7 @@
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
+import static com.android.internal.accessibility.common.ShortcutConstants.USER_SHORTCUT_TYPES;
import static com.android.internal.accessibility.util.AccessibilityStatsLogUtils.logAccessibilityShortcutActivated;
import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
@@ -111,6 +112,7 @@
import android.safetycenter.SafetyCenterManager;
import android.text.TextUtils;
import android.text.TextUtils.SimpleStringSplitter;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IntArray;
import android.util.Log;
@@ -145,9 +147,13 @@
import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.internal.accessibility.AccessibilityShortcutController.FrameworkFeatureInfo;
import com.android.internal.accessibility.AccessibilityShortcutController.LaunchableFrameworkFeatureInfo;
+import com.android.internal.accessibility.common.ShortcutConstants;
+import com.android.internal.accessibility.common.ShortcutConstants.FloatingMenuSize;
+import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
import com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity;
import com.android.internal.accessibility.util.AccessibilityUtils;
+import com.android.internal.accessibility.util.ShortcutUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
@@ -168,6 +174,7 @@
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.pm.UserManagerInternal;
import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.utils.Slogf;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
@@ -191,6 +198,7 @@
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
+import java.util.stream.Collectors;
/**
* This class is instantiated by the system as a system level service and can be
@@ -707,16 +715,6 @@
}
}
- private void onSomePackagesChangedLocked() {
- final AccessibilityUserState userState = getCurrentUserStateLocked();
- // Reload the installed services since some services may have different attributes
- // or resolve info (does not support equals), etc. Remove them then to force reload.
- userState.mInstalledServices.clear();
- if (readConfigurationForUserStateLocked(userState)) {
- onUserStateChangedLocked(userState);
- }
- }
-
private void onSomePackagesChangedLocked(
@Nullable List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos,
@Nullable List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) {
@@ -834,22 +832,16 @@
final int userId = getChangingUserId();
List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null;
List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null;
- if (Flags.scanPackagesWithoutLock()) {
- parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
- parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
- }
+ parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
+ parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
synchronized (mLock) {
// Only the profile parent can install accessibility services.
// Therefore we ignore packages from linked profiles.
if (userId != mCurrentUserId) {
return;
}
- if (Flags.scanPackagesWithoutLock()) {
- onSomePackagesChangedLocked(parsedAccessibilityServiceInfos,
- parsedAccessibilityShortcutInfos);
- } else {
- onSomePackagesChangedLocked();
- }
+ onSomePackagesChangedLocked(parsedAccessibilityServiceInfos,
+ parsedAccessibilityShortcutInfos);
}
}
@@ -867,10 +859,8 @@
final int userId = getChangingUserId();
List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null;
List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null;
- if (Flags.scanPackagesWithoutLock()) {
- parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
- parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
- }
+ parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
+ parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
synchronized (mLock) {
if (userId != mCurrentUserId) {
return;
@@ -885,12 +875,8 @@
// get a new one.
userState.mInstalledServices.clear();
final boolean configurationChanged;
- if (Flags.scanPackagesWithoutLock()) {
- configurationChanged = readConfigurationForUserStateLocked(userState,
- parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos);
- } else {
- configurationChanged = readConfigurationForUserStateLocked(userState);
- }
+ configurationChanged = readConfigurationForUserStateLocked(userState,
+ parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos);
if (reboundAService || configurationChanged) {
onUserStateChangedLocked(userState);
}
@@ -985,34 +971,6 @@
// package changes
mPackageMonitor.register(mContext, null, UserHandle.ALL, true);
- if (!Flags.deprecatePackageListObserver()) {
- final PackageManagerInternal pm = LocalServices.getService(
- PackageManagerInternal.class);
- if (pm != null) {
- pm.getPackageList(new PackageManagerInternal.PackageListObserver() {
- @Override
- public void onPackageAdded(String packageName, int uid) {
- final int userId = UserHandle.getUserId(uid);
- synchronized (mLock) {
- if (userId == mCurrentUserId) {
- onSomePackagesChangedLocked();
- }
- }
- }
-
- @Override
- public void onPackageRemoved(String packageName, int uid) {
- final int userId = UserHandle.getUserId(uid);
- synchronized (mLock) {
- if (userId == mCurrentUserId) {
- onPackageRemovedLocked(packageName);
- }
- }
- }
- });
- }
- }
-
// user change and unlock
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
@@ -1303,15 +1261,14 @@
// the computation for performance reasons.
boolean shouldComputeWindows = false;
int displayId = event.getDisplayId();
+ final int windowId = event.getWindowId();
+ if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID
+ && displayId == Display.INVALID_DISPLAY) {
+ displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowId(
+ resolvedUserId, windowId);
+ event.setDisplayId(displayId);
+ }
synchronized (mLock) {
- final int windowId = event.getWindowId();
- if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID
- && displayId == Display.INVALID_DISPLAY) {
- displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowIdLocked(
- resolvedUserId, windowId);
- event.setDisplayId(displayId);
- }
-
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
&& displayId != Display.INVALID_DISPLAY
&& mA11yWindowManager.isTrackingWindowsLocked(displayId)) {
@@ -1986,10 +1943,8 @@
mMagnificationController.updateUserIdIfNeeded(userId);
List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null;
List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null;
- if (Flags.scanPackagesWithoutLock()) {
- parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
- parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
- }
+ parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId);
+ parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId);
synchronized (mLock) {
if (mCurrentUserId == userId && mInitialized) {
return;
@@ -2014,12 +1969,8 @@
mCurrentUserId = userId;
AccessibilityUserState userState = getCurrentUserStateLocked();
- if (Flags.scanPackagesWithoutLock()) {
- readConfigurationForUserStateLocked(userState,
- parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos);
- } else {
- readConfigurationForUserStateLocked(userState);
- }
+ readConfigurationForUserStateLocked(userState,
+ parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos);
mSecurityPolicy.onSwitchUserLocked(mCurrentUserId, userState.mEnabledServices);
// Even if reading did not yield change, we have to update
// the state since the context in which the current user
@@ -2334,6 +2285,7 @@
if (!parsedAccessibilityServiceInfos.equals(userState.mInstalledServices)) {
userState.mInstalledServices.clear();
userState.mInstalledServices.addAll(parsedAccessibilityServiceInfos);
+ userState.updateTileServiceMapForAccessibilityServiceLocked();
return true;
}
return false;
@@ -2359,6 +2311,7 @@
if (!parsedAccessibilityShortcutInfos.equals(userState.mInstalledShortcuts)) {
userState.mInstalledShortcuts.clear();
userState.mInstalledShortcuts.addAll(parsedAccessibilityShortcutInfos);
+ userState.updateTileServiceMapForAccessibilityActivityLocked();
return true;
}
return false;
@@ -2621,6 +2574,12 @@
private <T> void persistColonDelimitedSetToSettingLocked(String settingName, int userId,
Set<T> set, Function<T, String> toString) {
+ persistColonDelimitedSetToSettingLocked(settingName, userId, set,
+ toString, /* defaultEmptyString= */ null);
+ }
+
+ private <T> void persistColonDelimitedSetToSettingLocked(String settingName, int userId,
+ Set<T> set, Function<T, String> toString, String defaultEmptyString) {
final StringBuilder builder = new StringBuilder();
for (T item : set) {
final String str = (item != null ? toString.apply(item) : null);
@@ -2636,7 +2595,18 @@
try {
final String settingValue = builder.toString();
Settings.Secure.putStringForUser(mContext.getContentResolver(),
- settingName, TextUtils.isEmpty(settingValue) ? null : settingValue, userId);
+ settingName,
+ TextUtils.isEmpty(settingValue) ? defaultEmptyString : settingValue, userId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ private void persistIntToSetting(int userId, String settingName, int settingValue) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Settings.Secure.putIntForUser(
+ mContext.getContentResolver(), settingName, settingValue, userId);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -3005,6 +2975,7 @@
scheduleUpdateClientsIfNeededLocked(userState, forceUpdate);
updateAccessibilityShortcutKeyTargetsLocked(userState);
updateAccessibilityButtonTargetsLocked(userState);
+ updateAccessibilityQsTargetsLocked(userState);
// Update the capabilities before the mode because we will check the current mode is
// invalid or not..
updateMagnificationCapabilitiesSettingsChangeLocked(userState);
@@ -3107,15 +3078,6 @@
userState.setFilterKeyEventsEnabledLocked(false);
}
- // ErrorProne doesn't understand that this method is only called while locked,
- // returning an error for accessing mCurrentUserId.
- @SuppressWarnings("GuardedBy")
- private boolean readConfigurationForUserStateLocked(AccessibilityUserState userState) {
- return readConfigurationForUserStateLocked(userState,
- parseAccessibilityServiceInfos(mCurrentUserId),
- parseAccessibilityShortcutInfos(mCurrentUserId));
- }
-
private boolean readConfigurationForUserStateLocked(
AccessibilityUserState userState,
List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos,
@@ -3132,6 +3094,7 @@
somethingChanged |= readMagnificationEnabledSettingsLocked(userState);
somethingChanged |= readAutoclickEnabledSettingLocked(userState);
somethingChanged |= readAccessibilityShortcutKeySettingLocked(userState);
+ somethingChanged |= readAccessibilityQsTargetsLocked(userState);
somethingChanged |= readAccessibilityButtonTargetsLocked(userState);
somethingChanged |= readAccessibilityButtonTargetComponentLocked(userState);
somethingChanged |= readUserRecommendedUiTimeoutSettingsLocked(userState);
@@ -3305,6 +3268,21 @@
return true;
}
+ private boolean readAccessibilityQsTargetsLocked(AccessibilityUserState userState) {
+ final Set<String> targetsFromSetting = new ArraySet<>();
+ readColonDelimitedSettingToSet(Settings.Secure.ACCESSIBILITY_QS_TARGETS,
+ userState.mUserId, str -> str, targetsFromSetting);
+
+ final Set<String> currentTargets =
+ userState.getShortcutTargetsLocked(UserShortcutType.QUICK_SETTINGS);
+ if (targetsFromSetting.equals(currentTargets)) {
+ return false;
+ }
+ userState.updateA11yQsTargetLocked(targetsFromSetting);
+ scheduleNotifyClientsOfServicesStateChangeLocked(userState);
+ return true;
+ }
+
private boolean readAccessibilityButtonTargetsLocked(AccessibilityUserState userState) {
final Set<String> targetsFromSetting = new ArraySet<>();
readColonDelimitedSettingToSet(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
@@ -3636,6 +3614,8 @@
final Set<String> shortcutKeyTargets =
userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY);
+ final Set<String> qsShortcutTargets =
+ userState.getShortcutTargetsLocked(UserShortcutType.QUICK_SETTINGS);
userState.mEnabledServices.forEach(componentName -> {
if (packageName != null && componentName != null
&& !packageName.equals(componentName.getPackageName())) {
@@ -3657,7 +3637,8 @@
return;
}
if (doesShortcutTargetsStringContain(buttonTargets, serviceName)
- || doesShortcutTargetsStringContain(shortcutKeyTargets, serviceName)) {
+ || doesShortcutTargetsStringContain(shortcutKeyTargets, serviceName)
+ || doesShortcutTargetsStringContain(qsShortcutTargets, serviceName)) {
return;
}
// For enabled a11y services targeting sdk version > Q and requesting a11y button should
@@ -3678,6 +3659,33 @@
}
/**
+ * Update the Settings.Secure.ACCESSIBILITY_QS_TARGETS so that it only contains valid content,
+ * and a side loaded service can't spoof the package name of the default service.
+ */
+ private void updateAccessibilityQsTargetsLocked(AccessibilityUserState userState) {
+ final Set<String> targets =
+ userState.getShortcutTargetsLocked(UserShortcutType.QUICK_SETTINGS);
+ final int lastSize = targets.size();
+ if (lastSize == 0) {
+ return;
+ }
+
+ // Removes the targets that are no longer installed on the device.
+ boolean somethingChanged = targets.removeIf(
+ name -> !userState.isShortcutTargetInstalledLocked(name));
+ if (!somethingChanged) {
+ return;
+ }
+ userState.updateA11yQsTargetLocked(targets);
+
+ // Update setting key with new value.
+ persistColonDelimitedSetToSettingLocked(
+ Settings.Secure.ACCESSIBILITY_QS_TARGETS,
+ userState.mUserId, targets, str -> str);
+ scheduleNotifyClientsOfServicesStateChangeLocked(userState);
+ }
+
+ /**
* Remove the shortcut target for the unbound service which is requesting accessibility button
* and targeting sdk > Q from the accessibility button and shortcut.
*
@@ -3691,19 +3699,42 @@
.targetSdkVersion <= Build.VERSION_CODES.Q) {
return;
}
+
+ final List<Pair<Integer, String>> shortcutTypeAndShortcutSetting = List.of(
+ new Pair<>(ACCESSIBILITY_SHORTCUT_KEY,
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE),
+ new Pair<>(ACCESSIBILITY_BUTTON,
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS),
+ new Pair<>(UserShortcutType.QUICK_SETTINGS,
+ Settings.Secure.ACCESSIBILITY_QS_TARGETS)
+ );
+
final ComponentName serviceName = service.getComponentName();
- if (userState.removeShortcutTargetLocked(ACCESSIBILITY_SHORTCUT_KEY, serviceName)) {
- final Set<String> currentTargets = userState.getShortcutTargetsLocked(
- ACCESSIBILITY_SHORTCUT_KEY);
- persistColonDelimitedSetToSettingLocked(
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
- userState.mUserId, currentTargets, str -> str);
- }
- if (userState.removeShortcutTargetLocked(ACCESSIBILITY_BUTTON, serviceName)) {
- final Set<String> currentTargets = userState.getShortcutTargetsLocked(
- ACCESSIBILITY_BUTTON);
- persistColonDelimitedSetToSettingLocked(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
- userState.mUserId, currentTargets, str -> str);
+ for (Pair<Integer, String> shortcutTypePair : shortcutTypeAndShortcutSetting) {
+ int shortcutType = shortcutTypePair.first;
+ String shortcutSettingName = shortcutTypePair.second;
+ if (userState.removeShortcutTargetLocked(shortcutType, serviceName)) {
+ final Set<String> currentTargets = userState.getShortcutTargetsLocked(shortcutType);
+ persistColonDelimitedSetToSettingLocked(
+ shortcutSettingName,
+ userState.mUserId, currentTargets, str -> str);
+
+ if (shortcutType != UserShortcutType.QUICK_SETTINGS) {
+ continue;
+ }
+
+ ComponentName tileService =
+ userState.getA11yFeatureToTileService().getOrDefault(serviceName, null);
+
+ final StatusBarManagerInternal statusBarManagerInternal =
+ LocalServices.getService(StatusBarManagerInternal.class);
+ // In case it's not initialized yet
+ if (statusBarManagerInternal == null || tileService == null) {
+ continue;
+ }
+ mMainHandler.sendMessage(obtainMessage(StatusBarManagerInternal::removeQsTile,
+ statusBarManagerInternal, tileService));
+ }
}
}
@@ -3967,6 +3998,262 @@
}
}
+ /**
+ * Turns on or off a shortcut type of the accessibility features. The {@code shortcutTypes} is a
+ * flag that contains values defined in the
+ * {@link ShortcutConstants.USER_SHORTCUT_TYPES}.
+ *
+ * @hide
+ */
+ @Override
+ public void enableShortcutsForTargets(
+ boolean enable, @UserShortcutType int shortcutTypes,
+ @NonNull List<String> shortcutTargets, @UserIdInt int userId) {
+ mContext.enforceCallingPermission(
+ Manifest.permission.MANAGE_ACCESSIBILITY, "enableShortcutsForTargets");
+ for (int shortcutType : USER_SHORTCUT_TYPES) {
+ if ((shortcutTypes & shortcutType) == shortcutType) {
+ enableShortcutForTargets(enable, shortcutType, shortcutTargets, userId);
+ }
+ }
+ }
+
+ private void enableShortcutForTargets(
+ boolean enable, @UserShortcutType int shortcutType,
+ @NonNull List<String> shortcutTargets, @UserIdInt int userId) {
+ final String shortcutTypeSettingKey = ShortcutUtils.convertToKey(shortcutType);
+ if (shortcutType == UserShortcutType.TRIPLETAP
+ || shortcutType == UserShortcutType.TWOFINGER_DOUBLETAP) {
+ for (String target : shortcutTargets) {
+ if (MAGNIFICATION_CONTROLLER_NAME.equals(target)) {
+ persistIntToSetting(
+ userId,
+ shortcutTypeSettingKey,
+ enable ? AccessibilityUtils.State.ON : AccessibilityUtils.State.OFF);
+ } else {
+ Slog.w(LOG_TAG,
+ "Triple tap or two-fingers double-tap is not supported for " + target);
+ }
+ }
+ return;
+ }
+ Set<String> validNewTargets;
+ Set<String> currentTargets;
+
+ Map<ComponentName, ComponentName> featureToTileMap =
+ getA11yFeatureToTileMapInternal(userId);
+ synchronized (mLock) {
+ AccessibilityUserState userState = getUserStateLocked(userId);
+ currentTargets =
+ ShortcutUtils.getShortcutTargetsFromSettings(mContext, shortcutType, userId);
+
+ Set<String> newTargets = new ArraySet<>(currentTargets);
+ if (enable) {
+ newTargets.addAll(shortcutTargets);
+ } else {
+ newTargets.removeAll(shortcutTargets);
+ }
+ validNewTargets = newTargets;
+
+ // filter out targets that doesn't have qs shortcut
+ if (shortcutType == UserShortcutType.QUICK_SETTINGS) {
+ validNewTargets = newTargets.stream().filter(target -> {
+ ComponentName targetComponent = ComponentName.unflattenFromString(target);
+ return featureToTileMap.containsKey(targetComponent);
+ }).collect(Collectors.toUnmodifiableSet());
+ }
+
+ if (currentTargets.equals(validNewTargets)) {
+ return;
+ }
+ persistColonDelimitedSetToSettingLocked(
+ shortcutTypeSettingKey,
+ userId,
+ validNewTargets,
+ str -> str,
+ /* defaultEmptyString= */ ""
+ );
+
+ if (shortcutType == UserShortcutType.QUICK_SETTINGS) {
+ userState.updateA11yQsTargetLocked(validNewTargets);
+ scheduleNotifyClientsOfServicesStateChangeLocked(userState);
+ onUserStateChangedLocked(userState);
+ }
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState(
+ mContext, new ArraySet<>(shortcutTargets), userId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+
+ // Add or Remove tile in QS Panel
+ if (shortcutType == UserShortcutType.QUICK_SETTINGS) {
+ mMainHandler.sendMessage(obtainMessage(
+ AccessibilityManagerService::updateA11yTileServicesInQuickSettingsPanel,
+ this, validNewTargets, currentTargets, userId));
+ }
+
+ if (!enable) {
+ return;
+ }
+ if (shortcutType == UserShortcutType.HARDWARE) {
+ skipVolumeShortcutDialogTimeoutRestriction(userId);
+ } else if (shortcutType == UserShortcutType.SOFTWARE) {
+ // Update the A11y FAB size to large when the Magnification shortcut is
+ // enabled and the user hasn't changed the floating button size
+ if (shortcutTargets.contains(MAGNIFICATION_CONTROLLER_NAME)
+ && Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
+ FloatingMenuSize.UNKNOWN, userId) == FloatingMenuSize.UNKNOWN) {
+ persistIntToSetting(
+ userId,
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
+ FloatingMenuSize.LARGE);
+ }
+ }
+ }
+
+ /**
+ * Add or remove the TileServices of the a11y features in the Quick Settings panel based on the
+ * changes in the new targets and current targets.
+ *
+ * <p>
+ * The framework tiles implemented in the SysUi are automatically added/removed via the
+ * SystemUI's AutoAddable framework. This method only handles updating the TileServices
+ * provided by AccessibilityService or Accessibility Activity.
+ * </p>
+ *
+ * @see com.android.systemui.qs.pipeline.domain.model.AutoAddable AutoAddable QS Tiles
+ */
+ private void updateA11yTileServicesInQuickSettingsPanel(
+ Set<String> newQsTargets,
+ Set<String> currentQsTargets, @UserIdInt int userId) {
+ // Call StatusBarManager to add/remove tiles
+ final StatusBarManagerInternal statusBarManagerInternal =
+ LocalServices.getService(StatusBarManagerInternal.class);
+ // In case it's not initialized yet
+ if (statusBarManagerInternal == null) {
+ return;
+ }
+
+ Map<ComponentName, ComponentName> a11yFeatureToTileMap =
+ getA11yFeatureToTileMapInternal(userId);
+ Set<String> targetWithNoTile = new ArraySet<>();
+
+ // Add TileServices to QS Panel that are added to the new targets
+ newQsTargets.stream()
+ .filter(target -> !currentQsTargets.contains(target))
+ .forEach(
+ target -> {
+ ComponentName targetComponent =
+ ComponentName.unflattenFromString(target);
+ if (targetComponent == null
+ || !a11yFeatureToTileMap.containsKey(targetComponent)) {
+ targetWithNoTile.add(target);
+ return;
+ }
+
+ if (ShortcutConstants.A11Y_FEATURE_TO_FRAMEWORK_TILE.containsKey(
+ targetComponent)) {
+ // a11y framework tile is handled by the SysUi autoaddable framework
+ return;
+ }
+ statusBarManagerInternal.addQsTileToFrontOrEnd(
+ a11yFeatureToTileMap.get(targetComponent), /* end= */ true);
+ }
+ );
+
+ // Remove TileServices from QS Panel that are no longer in the new targets.
+ currentQsTargets.stream()
+ .filter(target -> !newQsTargets.contains(target))
+ .forEach(
+ target -> {
+ ComponentName targetComponent =
+ ComponentName.unflattenFromString(target);
+ if (targetComponent == null
+ || !a11yFeatureToTileMap.containsKey(targetComponent)) {
+ targetWithNoTile.add(target);
+ return;
+ }
+
+ if (ShortcutConstants.A11Y_FEATURE_TO_FRAMEWORK_TILE.containsKey(
+ targetComponent)) {
+ // a11y framework tile is handled by the SysUi autoaddable framework
+ return;
+ }
+ statusBarManagerInternal.removeQsTile(
+ a11yFeatureToTileMap.get(targetComponent));
+ }
+ );
+
+ if (!targetWithNoTile.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Unable to add/remove Tiles for a11y features: " + targetWithNoTile
+ + "as the Tiles aren't provided");
+ }
+ }
+
+ @Override
+ public Bundle getA11yFeatureToTileMap(@UserIdInt int userId) {
+ mContext.enforceCallingPermission(
+ Manifest.permission.MANAGE_ACCESSIBILITY, "getA11yFeatureToTileMap");
+
+ Bundle bundle = new Bundle();
+ Map<ComponentName, ComponentName> a11yFeatureToTile =
+ getA11yFeatureToTileMapInternal(userId);
+ for (Map.Entry<ComponentName, ComponentName> entry : a11yFeatureToTile.entrySet()) {
+ bundle.putParcelable(entry.getKey().flattenToString(), entry.getValue());
+ }
+ return bundle;
+ }
+
+
+
+ /**
+ * Returns accessibility feature's component and the provided tile map. This includes the
+ * TileService provided by the AccessibilityService or Accessibility Activity and the tile
+ * component provided by the framework's feature.
+ *
+ * @return a map of a feature's component name, and its provided tile's component name. The
+ * returned map's keys and values are not null. If a feature doesn't provide a tile, it won't
+ * have an entry in this map.
+ *
+ * @see ShortcutConstants.A11Y_FEATURE_TO_FRAMEWORK_TILE
+ */
+ @NonNull
+ private Map<ComponentName, ComponentName> getA11yFeatureToTileMapInternal(
+ @UserIdInt int userId) {
+ final Map<ComponentName, ComponentName> a11yFeatureToTileService;
+ Map<ComponentName, ComponentName> a11yFeatureToTile = new ArrayMap<>();
+ final int resolvedUserId;
+
+ synchronized (mLock) {
+ resolvedUserId = mSecurityPolicy
+ .resolveCallingUserIdEnforcingPermissionsLocked(userId);
+ AccessibilityUserState userState = getUserStateLocked(resolvedUserId);
+ a11yFeatureToTileService = userState.getA11yFeatureToTileService();
+ }
+ final boolean shouldFilterAppAccess = Binder.getCallingPid() != OWN_PROCESS_ID;
+ final int callingUid = Binder.getCallingUid();
+ final PackageManagerInternal pm = LocalServices.getService(
+ PackageManagerInternal.class);
+
+ for (Map.Entry<ComponentName, ComponentName> entry :
+ a11yFeatureToTileService.entrySet()) {
+ if (shouldFilterAppAccess
+ && pm.filterAppAccess(
+ entry.getKey().getPackageName(), callingUid, resolvedUserId)) {
+ continue;
+ }
+ a11yFeatureToTile.put(entry.getKey(), entry.getValue());
+ }
+
+ a11yFeatureToTile.putAll(ShortcutConstants.A11Y_FEATURE_TO_FRAMEWORK_TILE);
+ return a11yFeatureToTile;
+ }
+
@Override
public List<String> getAccessibilityShortcutTargets(@ShortcutType int shortcutType) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
@@ -5836,4 +6123,15 @@
}
}
}
+
+
+ /**
+ * Bypasses the timeout restriction if volume key shortcut assigned.
+ */
+ private void skipVolumeShortcutDialogTimeoutRestriction(int userId) {
+ persistIntToSetting(
+ userId,
+ Settings.Secure.SKIP_ACCESSIBILITY_SHORTCUT_DIALOG_TIMEOUT_RESTRICTION,
+ /* true */ 1);
+ }
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 68ee780..063eafe 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -38,10 +38,12 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.os.Binder;
import android.os.RemoteCallbackList;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
@@ -51,6 +53,7 @@
import com.android.internal.R;
import com.android.internal.accessibility.AccessibilityShortcutController;
+import com.android.internal.accessibility.common.ShortcutConstants;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -99,6 +102,7 @@
final ArraySet<String> mAccessibilityShortcutKeyTargets = new ArraySet<>();
final ArraySet<String> mAccessibilityButtonTargets = new ArraySet<>();
+ private final ArraySet<String> mAccessibilityQsTargets = new ArraySet<>();
private final ServiceInfoChangeListener mServiceInfoChangeListener;
@@ -146,6 +150,8 @@
private final int mFocusStrokeWidthDefaultValue;
// The default value of the focus color.
private final int mFocusColorDefaultValue;
+ private final Map<ComponentName, ComponentName> mA11yServiceToTileService = new ArrayMap<>();
+ private final Map<ComponentName, ComponentName> mA11yActivityToTileService = new ArrayMap<>();
private Context mContext;
@@ -560,6 +566,8 @@
pw.println("}");
pw.append(" button target:{").append(mTargetAssignedToAccessibilityButton);
pw.println("}");
+ pw.append(" qs shortcut targets:" + mAccessibilityQsTargets);
+ pw.println();
pw.append(" Bound services:{");
final int serviceCount = mBoundServices.size();
for (int j = 0; j < serviceCount; j++) {
@@ -762,6 +770,8 @@
return mAccessibilityShortcutKeyTargets;
} else if (shortcutType == ACCESSIBILITY_BUTTON) {
return mAccessibilityButtonTargets;
+ } else if (shortcutType == ShortcutConstants.UserShortcutType.QUICK_SETTINGS) {
+ return getA11yQsTargets();
}
return null;
}
@@ -808,7 +818,8 @@
*/
public boolean removeShortcutTargetLocked(@ShortcutType int shortcutType,
ComponentName target) {
- return getShortcutTargetsLocked(shortcutType).removeIf(name -> {
+ Set<String> targets = getShortcutTargetsLocked(shortcutType);
+ boolean result = targets.removeIf(name -> {
ComponentName componentName;
if (name == null
|| (componentName = ComponentName.unflattenFromString(name)) == null) {
@@ -816,6 +827,11 @@
}
return componentName.equals(target);
});
+ if (shortcutType == ShortcutConstants.UserShortcutType.QUICK_SETTINGS) {
+ updateA11yQsTargetLocked(targets);
+ }
+
+ return result;
}
/**
@@ -1034,4 +1050,60 @@
}
return false;
}
+
+ public void updateTileServiceMapForAccessibilityServiceLocked() {
+ mA11yServiceToTileService.clear();
+ mInstalledServices.forEach(
+ a11yServiceInfo -> {
+ String tileServiceName = a11yServiceInfo.getTileServiceName();
+ if (!TextUtils.isEmpty(tileServiceName)) {
+ ResolveInfo resolveInfo = a11yServiceInfo.getResolveInfo();
+ ComponentName a11yFeature = new ComponentName(
+ resolveInfo.serviceInfo.packageName,
+ resolveInfo.serviceInfo.name
+ );
+ ComponentName tileService = new ComponentName(
+ a11yFeature.getPackageName(),
+ tileServiceName
+ );
+ mA11yServiceToTileService.put(a11yFeature, tileService);
+ }
+ }
+ );
+ }
+
+ public void updateTileServiceMapForAccessibilityActivityLocked() {
+ mA11yActivityToTileService.clear();
+ mInstalledShortcuts.forEach(
+ a11yShortcutInfo -> {
+ String tileServiceName = a11yShortcutInfo.getTileServiceName();
+ if (!TextUtils.isEmpty(tileServiceName)) {
+ ComponentName a11yFeature = a11yShortcutInfo.getComponentName();
+ ComponentName tileService = new ComponentName(
+ a11yFeature.getPackageName(),
+ tileServiceName);
+ mA11yActivityToTileService.put(a11yFeature, tileService);
+ }
+ }
+ );
+ }
+
+ public void updateA11yQsTargetLocked(Set<String> targets) {
+ mAccessibilityQsTargets.clear();
+ mAccessibilityQsTargets.addAll(targets);
+ }
+
+ /**
+ * Returns a copy of the targets which has qs shortcut turned on
+ */
+ public ArraySet<String> getA11yQsTargets() {
+ return new ArraySet<>(mAccessibilityQsTargets);
+ }
+
+ public Map<ComponentName, ComponentName> getA11yFeatureToTileService() {
+ Map<ComponentName, ComponentName> featureToTileServiceMap = new ArrayMap<>();
+ featureToTileServiceMap.putAll(mA11yServiceToTileService);
+ featureToTileServiceMap.putAll(mA11yActivityToTileService);
+ return featureToTileServiceMap;
+ }
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index b818150..8c06bc8 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -2038,8 +2038,11 @@
* @param windowId The windowId
* @return The display ID
*/
- public int getDisplayIdByUserIdAndWindowIdLocked(int userId, int windowId) {
- final IBinder windowToken = getWindowTokenForUserAndWindowIdLocked(userId, windowId);
+ public int getDisplayIdByUserIdAndWindowId(int userId, int windowId) {
+ final IBinder windowToken;
+ synchronized (mLock) {
+ windowToken = getWindowTokenForUserAndWindowIdLocked(userId, windowId);
+ }
if (traceWMEnabled()) {
logTraceWM("getDisplayIdForWindow", "token=" + windowToken);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index 4fc65bf..be2ad21 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -1601,6 +1601,9 @@
+ " pointers down.");
return;
}
+ if (Flags.resetHoverEventTimerOnActionUp() && mEvents.size() == 0) {
+ return;
+ }
// Send an accessibility event to announce the touch exploration start.
mDispatcher.sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_START);
if (isSendMotionEventsEnabled()) {
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
index d9e25ef..e13994e 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
@@ -525,8 +525,9 @@
mReceivedPointersDown |= pointerFlag;
mReceivedPointers[pointerId].set(
event.getX(pointerIndex), event.getY(pointerIndex), event.getEventTime());
-
- mPrimaryPointerId = pointerId;
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mPrimaryPointerId = pointerId;
+ }
}
/**
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 1749ee3..16fe007 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -4025,8 +4025,9 @@
}
}
}
- throw new IllegalArgumentException(
- providerComponent + " is not a valid AppWidget provider");
+ // Either the provider does not exist or the caller does not have permission to access its
+ // previews.
+ return null;
}
@Override
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 285e54c..e1291e5 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -33,10 +33,8 @@
import android.annotation.Nullable;
import android.app.ActivityManagerInternal;
import android.content.ComponentName;
-import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.graphics.Rect;
import android.metrics.LogMaker;
@@ -253,26 +251,6 @@
@Override // from PerUserSystemService
protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
throws NameNotFoundException {
- final List<ResolveInfo> resolveInfos =
- getContext().getPackageManager().queryIntentServicesAsUser(
- new Intent(AutofillService.SERVICE_INTERFACE),
- PackageManager.GET_META_DATA,
- mUserId);
- boolean currentPackageStillHasAutofillIntentFilter = false;
- for (ResolveInfo resolveInfo : resolveInfos) {
- final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
- if (serviceInfo.getComponentName().equals(serviceComponent)) {
- currentPackageStillHasAutofillIntentFilter = true;
- break;
- }
- }
- if (!currentPackageStillHasAutofillIntentFilter) {
- Slog.w(TAG,
- "Autofill service from '" + serviceComponent.getPackageName() + "' does"
- + "not have intent filter " + AutofillService.SERVICE_INTERFACE);
- throw new SecurityException("Service does not declare intent filter "
- + AutofillService.SERVICE_INTERFACE);
- }
mInfo = new AutofillServiceInfo(getContext(), serviceComponent, mUserId);
return mInfo.getServiceInfo();
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 7afb780..a55b8d0 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -171,6 +171,7 @@
import android.util.TimeUtils;
import android.view.KeyEvent;
import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillFeatureFlags;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillManager.AutofillCommitReason;
import android.view.autofill.AutofillManager.SmartSuggestionMode;
@@ -1246,7 +1247,8 @@
@GuardedBy("mLock")
private void requestNewFillResponseLocked(@NonNull ViewState viewState, int newState,
int flags) {
- final FillResponse existingResponse = shouldRequestSecondaryProvider(flags)
+ boolean isSecondary = shouldRequestSecondaryProvider(flags);
+ final FillResponse existingResponse = isSecondary
? viewState.getSecondaryResponse() : viewState.getResponse();
mFillRequestEventLogger.startLogForNewRequest();
mRequestCount++;
@@ -1283,12 +1285,7 @@
}
viewState.setState(newState);
-
- int requestId;
- // TODO(b/158623971): Update this to prevent possible overflow
- do {
- requestId = sIdCounter.getAndIncrement();
- } while (requestId == INVALID_REQUEST_ID);
+ int requestId = getRequestId(isSecondary);
// Create a metrics log for the request
final int ordinal = mRequestLogs.size() + 1;
@@ -1367,6 +1364,25 @@
requestAssistStructureLocked(requestId, flags);
}
+ private static int getRequestId(boolean isSecondary) {
+ // For authentication flows, there needs to be a way to know whether to retrieve the Fill
+ // Response from the primary provider or the secondary provider from the requestId. A simple
+ // way to achieve this is by assigning odd number request ids to secondary provider and
+ // even numbers to primary provider.
+ int requestId;
+ // TODO(b/158623971): Update this to prevent possible overflow
+ if (isSecondary) {
+ do {
+ requestId = sIdCounter.getAndIncrement();
+ } while (!isSecondaryProviderRequestId(requestId));
+ } else {
+ do {
+ requestId = sIdCounter.getAndIncrement();
+ } while (requestId == INVALID_REQUEST_ID || isSecondaryProviderRequestId(requestId));
+ }
+ return requestId;
+ }
+
private boolean isRequestSupportFillDialog(int flags) {
return (flags & FLAG_SUPPORTS_FILL_DIALOG) != 0;
}
@@ -1483,7 +1499,7 @@
mSessionCommittedEventLogger.maybeSetComponentPackageUid(uid);
mSaveEventLogger = SaveEventLogger.forSessionId(sessionId);
mIsPrimaryCredential = isPrimaryCredential;
- mIgnoreViewStateResetToEmpty = Flags.ignoreViewStateResetToEmpty();
+ mIgnoreViewStateResetToEmpty = AutofillFeatureFlags.shouldIgnoreViewStateResetToEmpty();
synchronized (mLock) {
mSessionFlags = new SessionFlags();
@@ -2790,7 +2806,9 @@
removeFromService();
return;
}
- final FillResponse authenticatedResponse = mResponses.get(requestId);
+ final FillResponse authenticatedResponse = isSecondaryProviderRequestId(requestId)
+ ? mSecondaryResponses.get(requestId)
+ : mResponses.get(requestId);
if (authenticatedResponse == null || data == null) {
Slog.w(TAG, "no authenticated response");
mPresentationStatsEventLogger.maybeSetAuthenticationResult(
@@ -2915,6 +2933,10 @@
}
}
+ private static boolean isSecondaryProviderRequestId(int requestId) {
+ return requestId % 2 == 1;
+ }
+
private Dataset getDatasetFromCredentialResponse(GetCredentialResponse result) {
if (result == null) {
return null;
@@ -6437,12 +6459,12 @@
} else if (response != null) {
if (viewId.isVirtualInt()) {
ViewNode viewNode = getViewNodeFromContextsLocked(viewId);
- if (viewNode != null && viewNode.getCredentialManagerCallback() != null) {
+ if (viewNode != null && viewNode.getPendingCredentialCallback() != null) {
Bundle resultData = new Bundle();
resultData.putParcelable(
CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
response);
- viewNode.getCredentialManagerCallback().send(SUCCESS_CREDMAN_SELECTOR,
+ viewNode.getPendingCredentialCallback().send(SUCCESS_CREDMAN_SELECTOR,
resultData);
} else {
Slog.w(TAG, "View node not found after GetCredentialResponse");
diff --git a/services/backup/BACKUP_OWNERS b/services/backup/BACKUP_OWNERS
index f8f4f4f..29ae202 100644
--- a/services/backup/BACKUP_OWNERS
+++ b/services/backup/BACKUP_OWNERS
@@ -2,9 +2,10 @@
jstemmer@google.com
martinoh@google.com
-millmore@google.com
niamhfw@google.com
piee@google.com
philippov@google.com
rthakohov@google.com
-sarpm@google.com
\ No newline at end of file
+sarpm@google.com
+beatricemarch@google.com
+azilio@google.com
\ No newline at end of file
diff --git a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
index 6e98e68..1f8736b 100644
--- a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
+++ b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
@@ -24,7 +24,6 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.Signature;
import android.content.pm.SigningInfo;
@@ -32,7 +31,7 @@
import android.os.ParcelFileDescriptor;
import android.util.Slog;
-import com.android.server.LocalServices;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.backup.utils.BackupEligibilityRules;
import java.io.BufferedInputStream;
@@ -49,16 +48,14 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.Objects;
import java.util.Set;
/**
- * We back up the signatures of each package so that during a system restore,
- * we can verify that the app whose data we think we have matches the app
- * actually resident on the device.
+ * We back up the signatures of each package so that during a system restore, we can verify that the
+ * app whose data we think we have matches the app actually resident on the device.
*
- * Since the Package Manager isn't a proper "application" we just provide a
- * direct IBackupAgent implementation and hand-construct it at need.
+ * <p>Since the Package Manager isn't a proper "application" we just provide a direct IBackupAgent
+ * implementation and hand-construct it at need.
*/
public class PackageManagerBackupAgent extends BackupAgent {
private static final String TAG = "PMBA";
@@ -66,7 +63,7 @@
// key under which we store global metadata (individual app metadata
// is stored using the package name as a key)
- private static final String GLOBAL_METADATA_KEY = "@meta@";
+ @VisibleForTesting static final String GLOBAL_METADATA_KEY = "@meta@";
// key under which we store the identity of the user's chosen default home app
private static final String DEFAULT_HOME_KEY = "@home@";
@@ -76,19 +73,19 @@
// ANCESTRAL_RECORD_VERSION=1 (introduced Android P).
// Should the ANCESTRAL_RECORD_VERSION be bumped up in the future, STATE_FILE_VERSION will also
// need bumping up, assuming more data needs saving to the state file.
- private static final String STATE_FILE_HEADER = "=state=";
- private static final int STATE_FILE_VERSION = 2;
+ @VisibleForTesting static final String STATE_FILE_HEADER = "=state=";
+ @VisibleForTesting static final int STATE_FILE_VERSION = 2;
// key under which we store the saved ancestral-dataset format (starting from Android P)
// IMPORTANT: this key needs to come first in the restore data stream (to find out
// whether this version of Android knows how to restore the incoming data set), so it needs
// to be always the first one in alphabetical order of all the keys
- private static final String ANCESTRAL_RECORD_KEY = "@ancestral_record@";
+ @VisibleForTesting static final String ANCESTRAL_RECORD_KEY = "@ancestral_record@";
// Current version of the saved ancestral-dataset format
// Note that this constant was not used until Android P, and started being used
// to version @pm@ data for forwards-compatibility.
- private static final int ANCESTRAL_RECORD_VERSION = 1;
+ @VisibleForTesting static final int ANCESTRAL_RECORD_VERSION = 1;
// Undefined version of the saved ancestral-dataset file format means that the restore data
// is coming from pre-Android P device.
@@ -134,8 +131,8 @@
init(packageMgr, packages, userId);
}
- public PackageManagerBackupAgent(PackageManager packageMgr, int userId,
- BackupEligibilityRules backupEligibilityRules) {
+ public PackageManagerBackupAgent(
+ PackageManager packageMgr, int userId, BackupEligibilityRules backupEligibilityRules) {
init(packageMgr, null, userId);
evaluateStorablePackages(backupEligibilityRules);
@@ -159,12 +156,12 @@
}
/** Gets all packages installed on user {@code userId} eligible for backup. */
- public static List<PackageInfo> getStorableApplications(PackageManager pm, int userId,
- BackupEligibilityRules backupEligibilityRules) {
+ public static List<PackageInfo> getStorableApplications(
+ PackageManager pm, int userId, BackupEligibilityRules backupEligibilityRules) {
List<PackageInfo> pkgs =
pm.getInstalledPackagesAsUser(PackageManager.GET_SIGNING_CERTIFICATES, userId);
int N = pkgs.size();
- for (int a = N-1; a >= 0; a--) {
+ for (int a = N - 1; a >= 0; a--) {
PackageInfo pkg = pkgs.get(a);
if (!backupEligibilityRules.appIsEligibleForBackup(pkg.applicationInfo)) {
pkgs.remove(a);
@@ -204,12 +201,14 @@
return mRestoredSignatures.keySet();
}
- // The backed up data is the signature block for each app, keyed by the package name.
- public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
- ParcelFileDescriptor newState) {
+ @Override
+ public void onBackup(
+ ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) {
if (DEBUG) Slog.v(TAG, "onBackup()");
- ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream(); // we'll reuse these
+ // The backed up data is the signature block for each app, keyed by the package name.
+
+ ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream(); // we'll reuse these
DataOutputStream outputBufferStream = new DataOutputStream(outputBuffer);
parseStateFile(oldState);
@@ -218,8 +217,13 @@
// "already backed up" map built by parseStateFile().
if (mStoredIncrementalVersion == null
|| !mStoredIncrementalVersion.equals(Build.VERSION.INCREMENTAL)) {
- Slog.i(TAG, "Previous metadata " + mStoredIncrementalVersion + " mismatch vs "
- + Build.VERSION.INCREMENTAL + " - rewriting");
+ Slog.i(
+ TAG,
+ "Previous metadata "
+ + mStoredIncrementalVersion
+ + " mismatch vs "
+ + Build.VERSION.INCREMENTAL
+ + " - rewriting");
mExisting.clear();
}
@@ -271,8 +275,9 @@
} else {
PackageInfo info = null;
try {
- info = mPackageManager.getPackageInfoAsUser(packName,
- PackageManager.GET_SIGNING_CERTIFICATES, mUserId);
+ info =
+ mPackageManager.getPackageInfoAsUser(
+ packName, PackageManager.GET_SIGNING_CERTIFICATES, mUserId);
} catch (NameNotFoundException e) {
// Weird; we just found it, and now are told it doesn't exist.
// Treat it as having been removed from the device.
@@ -294,8 +299,11 @@
SigningInfo signingInfo = info.signingInfo;
if (signingInfo == null) {
- Slog.w(TAG, "Not backing up package " + packName
- + " since it appears to have no signatures.");
+ Slog.w(
+ TAG,
+ "Not backing up package "
+ + packName
+ + " since it appears to have no signatures.");
continue;
}
@@ -317,15 +325,20 @@
}
// retrieve the newest sigs to back up
Signature[] infoSignatures = signingInfo.getApkContentsSigners();
- writeSignatureHashArray(outputBufferStream,
- BackupUtils.hashSignatureArray(infoSignatures));
+ writeSignatureHashArray(
+ outputBufferStream, BackupUtils.hashSignatureArray(infoSignatures));
if (DEBUG) {
- Slog.v(TAG, "+ writing metadata for " + packName
- + " version=" + info.getLongVersionCode()
- + " entityLen=" + outputBuffer.size());
+ Slog.v(
+ TAG,
+ "+ writing metadata for "
+ + packName
+ + " version="
+ + info.getLongVersionCode()
+ + " entityLen="
+ + outputBuffer.size());
}
-
+
// Now we can write the backup entity for this package
writeEntity(data, packName, outputBuffer.toByteArray());
}
@@ -363,13 +376,15 @@
data.writeEntityData(bytes, bytes.length);
}
- // "Restore" here is a misnomer. What we're really doing is reading back the
- // set of app signatures associated with each backed-up app in this restore
- // image. We'll use those later to determine what we can legitimately restore.
+ @Override
public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
throws IOException {
if (DEBUG) Slog.v(TAG, "onRestore()");
+ // "Restore" here is a misnomer. What we're really doing is reading back the
+ // set of app signatures associated with each backed-up app in this restore
+ // image. We'll use those later to determine what we can legitimately restore.
+
// we expect the ANCESTRAL_RECORD_KEY ("@ancestral_record@") to always come first in the
// restore set - based on that value we use different mechanisms to consume the data;
// if the ANCESTRAL_RECORD_KEY is missing in the restore set, it means that the data is
@@ -380,8 +395,10 @@
RestoreDataConsumer consumer = getRestoreDataConsumer(ancestralRecordVersion);
if (consumer == null) {
- Slog.w(TAG, "Ancestral restore set version is unknown"
- + " to this Android version; not restoring");
+ Slog.w(
+ TAG,
+ "Ancestral restore set version is unknown"
+ + " to this Android version; not restoring");
return;
} else {
consumer.consumeRestoreData(data);
@@ -443,9 +460,9 @@
Slog.w(TAG, "Read empty signature block");
return null;
}
-
+
if (DEBUG) Slog.v(TAG, " ... unflatten read " + num);
-
+
// Sensical?
if (num > 20) {
Slog.e(TAG, "Suspiciously large sig count in restore data; aborting");
@@ -506,8 +523,11 @@
if (pkg.equals(STATE_FILE_HEADER)) {
int stateVersion = in.readInt();
if (stateVersion > STATE_FILE_VERSION) {
- Slog.w(TAG, "Unsupported state file version " + stateVersion
- + ", redoing from start");
+ Slog.w(
+ TAG,
+ "Unsupported state file version "
+ + stateVersion
+ + ", redoing from start");
return;
}
pkg = in.readUTF();
@@ -574,7 +594,8 @@
}
// Util: write out our new backup state file
- private void writeStateFile(List<PackageInfo> pkgs, ParcelFileDescriptor stateFile) {
+ @VisibleForTesting
+ static void writeStateFile(List<PackageInfo> pkgs, ParcelFileDescriptor stateFile) {
FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor());
BufferedOutputStream outbuf = new BufferedOutputStream(outstream);
DataOutputStream out = new DataOutputStream(outbuf);
@@ -640,10 +661,17 @@
mStoredIncrementalVersion = inputBufferStream.readUTF();
mHasMetadata = true;
if (DEBUG) {
- Slog.i(TAG, "Restore set version " + storedSystemVersion
- + " is compatible with OS version " + Build.VERSION.SDK_INT
- + " (" + mStoredIncrementalVersion + " vs "
- + Build.VERSION.INCREMENTAL + ")");
+ Slog.i(
+ TAG,
+ "Restore set version "
+ + storedSystemVersion
+ + " is compatible with OS version "
+ + Build.VERSION.SDK_INT
+ + " ("
+ + mStoredIncrementalVersion
+ + " vs "
+ + Build.VERSION.INCREMENTAL
+ + ")");
}
} else if (key.equals(DEFAULT_HOME_KEY)) {
String cn = inputBufferStream.readUTF();
@@ -652,10 +680,16 @@
mRestoredHomeInstaller = inputBufferStream.readUTF();
mRestoredHomeSigHashes = readSignatureHashArray(inputBufferStream);
if (DEBUG) {
- Slog.i(TAG, " read preferred home app " + mRestoredHome
- + " version=" + mRestoredHomeVersion
- + " installer=" + mRestoredHomeInstaller
- + " sig=" + mRestoredHomeSigHashes);
+ Slog.i(
+ TAG,
+ " read preferred home app "
+ + mRestoredHome
+ + " version="
+ + mRestoredHomeVersion
+ + " installer="
+ + mRestoredHomeInstaller
+ + " sig="
+ + mRestoredHomeSigHashes);
}
} else {
// it's a file metadata record
@@ -668,14 +702,24 @@
}
ArrayList<byte[]> sigs = readSignatureHashArray(inputBufferStream);
if (DEBUG) {
- Slog.i(TAG, " read metadata for " + key
- + " dataSize=" + dataSize
- + " versionCode=" + versionCode + " sigs=" + sigs);
+ Slog.i(
+ TAG,
+ " read metadata for "
+ + key
+ + " dataSize="
+ + dataSize
+ + " versionCode="
+ + versionCode
+ + " sigs="
+ + sigs);
}
if (sigs == null || sigs.size() == 0) {
- Slog.w(TAG, "Not restoring package " + key
- + " since it appears to have no signatures.");
+ Slog.w(
+ TAG,
+ "Not restoring package "
+ + key
+ + " since it appears to have no signatures.");
continue;
}
@@ -687,8 +731,12 @@
boolean readNextHeader = data.readNextHeader();
if (!readNextHeader) {
- if (DEBUG) Slog.v(TAG, "LegacyRestoreDataConsumer:"
- + " we're done reading all the headers");
+ if (DEBUG) {
+ Slog.v(
+ TAG,
+ "LegacyRestoreDataConsumer:"
+ + " we're done reading all the headers");
+ }
break;
}
}
@@ -725,10 +773,17 @@
mStoredIncrementalVersion = inputBufferStream.readUTF();
mHasMetadata = true;
if (DEBUG) {
- Slog.i(TAG, "Restore set version " + storedSystemVersion
- + " is compatible with OS version " + Build.VERSION.SDK_INT
- + " (" + mStoredIncrementalVersion + " vs "
- + Build.VERSION.INCREMENTAL + ")");
+ Slog.i(
+ TAG,
+ "Restore set version "
+ + storedSystemVersion
+ + " is compatible with OS version "
+ + Build.VERSION.SDK_INT
+ + " ("
+ + mStoredIncrementalVersion
+ + " vs "
+ + Build.VERSION.INCREMENTAL
+ + ")");
}
} else if (key.equals(DEFAULT_HOME_KEY)) {
// Default home app data is no longer backed up by this agent. This code is
@@ -739,10 +794,16 @@
mRestoredHomeInstaller = inputBufferStream.readUTF();
mRestoredHomeSigHashes = readSignatureHashArray(inputBufferStream);
if (DEBUG) {
- Slog.i(TAG, " read preferred home app " + mRestoredHome
- + " version=" + mRestoredHomeVersion
- + " installer=" + mRestoredHomeInstaller
- + " sig=" + mRestoredHomeSigHashes);
+ Slog.i(
+ TAG,
+ " read preferred home app "
+ + mRestoredHome
+ + " version="
+ + mRestoredHomeVersion
+ + " installer="
+ + mRestoredHomeInstaller
+ + " sig="
+ + mRestoredHomeSigHashes);
}
} else {
// it's a file metadata record
@@ -755,14 +816,24 @@
}
ArrayList<byte[]> sigs = readSignatureHashArray(inputBufferStream);
if (DEBUG) {
- Slog.i(TAG, " read metadata for " + key
- + " dataSize=" + dataSize
- + " versionCode=" + versionCode + " sigs=" + sigs);
+ Slog.i(
+ TAG,
+ " read metadata for "
+ + key
+ + " dataSize="
+ + dataSize
+ + " versionCode="
+ + versionCode
+ + " sigs="
+ + sigs);
}
if (sigs == null || sigs.size() == 0) {
- Slog.w(TAG, "Not restoring package " + key
- + " since it appears to have no signatures.");
+ Slog.w(
+ TAG,
+ "Not restoring package "
+ + key
+ + " since it appears to have no signatures.");
continue;
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index e4a1048..3846e98 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -244,13 +244,14 @@
context, mAssociationStore, mObservableUuidStore, mDevicePresenceMonitor,
mPowerManagerInternal);
+ mTransportManager = new CompanionTransportManager(context, mAssociationStore);
+
mAssociationRevokeProcessor = new AssociationRevokeProcessor(this, mAssociationStore,
mPackageManagerInternal, mDevicePresenceMonitor, mCompanionAppController,
- mSystemDataTransferRequestStore);
+ mSystemDataTransferRequestStore, mTransportManager);
loadAssociationsFromDisk();
- mTransportManager = new CompanionTransportManager(context, mAssociationStore);
mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
mPackageManagerInternal, mAssociationStore,
mSystemDataTransferRequestStore, mTransportManager);
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRevokeProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRevokeProcessor.java
index 490be0d..d1efbbc 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRevokeProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRevokeProcessor.java
@@ -42,6 +42,7 @@
import com.android.server.companion.CompanionDeviceManagerService;
import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
+import com.android.server.companion.transport.CompanionTransportManager;
import java.util.HashMap;
import java.util.Map;
@@ -62,6 +63,7 @@
private final @NonNull CompanionDevicePresenceMonitor mDevicePresenceMonitor;
private final @NonNull SystemDataTransferRequestStore mSystemDataTransferRequestStore;
private final @NonNull CompanionApplicationController mCompanionAppController;
+ private final @NonNull CompanionTransportManager mTransportManager;
private final OnPackageVisibilityChangeListener mOnPackageVisibilityChangeListener;
private final ActivityManager mActivityManager;
@@ -97,7 +99,8 @@
@NonNull PackageManagerInternal packageManager,
@NonNull CompanionDevicePresenceMonitor devicePresenceMonitor,
@NonNull CompanionApplicationController applicationController,
- @NonNull SystemDataTransferRequestStore systemDataTransferRequestStore) {
+ @NonNull SystemDataTransferRequestStore systemDataTransferRequestStore,
+ @NonNull CompanionTransportManager companionTransportManager) {
mService = service;
mContext = service.getContext();
mActivityManager = mContext.getSystemService(ActivityManager.class);
@@ -108,6 +111,7 @@
mDevicePresenceMonitor = devicePresenceMonitor;
mCompanionAppController = applicationController;
mSystemDataTransferRequestStore = systemDataTransferRequestStore;
+ mTransportManager = companionTransportManager;
}
/**
@@ -120,6 +124,9 @@
final String packageName = association.getPackageName();
final String deviceProfile = association.getDeviceProfile();
+ // Detach transport if exists
+ mTransportManager.detachSystemDataTransport(packageName, userId, associationId);
+
if (!maybeRemoveRoleHolderForAssociation(association)) {
// Need to remove the app from list of the role holders, but will have to do it later
// (the app is in foreground at the moment).
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 6dd14ac..793fb7f 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -153,12 +153,12 @@
public void detachSystemDataTransport(String packageName, int userId, int associationId) {
synchronized (mTransports) {
- final Transport transport = mTransports.get(associationId);
- if (transport != null) {
- mTransports.delete(associationId);
- transport.stop();
+ final Transport transport = mTransports.removeReturnOld(associationId);
+ if (transport == null) {
+ return;
}
+ transport.stop();
notifyOnTransportsChanged();
}
}
diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
index 82ab098..340bc32 100644
--- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
+++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
@@ -38,7 +38,8 @@
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
- ]
+ ],
+ "keywords": ["primary-device"]
},
{
"name": "CtsHardwareTestCases",
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 b1672ed..8244d20 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -29,6 +29,7 @@
import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+import static android.companion.virtualdevice.flags.Flags.virtualCameraServiceDiscovery;
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
@@ -111,6 +112,8 @@
import com.android.server.companion.virtual.camera.VirtualCameraController;
import com.android.server.inputmethod.InputMethodManagerInternal;
+import dalvik.annotation.optimization.FastNative;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.List;
@@ -265,7 +268,7 @@
runningAppsChangedCallback,
params,
DisplayManagerGlobal.getInstance(),
- Flags.virtualCamera()
+ isVirtualCameraEnabled()
? new VirtualCameraController(params.getDevicePolicy(POLICY_TYPE_CAMERA))
: null);
}
@@ -1535,4 +1538,13 @@
return mToken;
}
}
+
+ private static boolean isVirtualCameraEnabled() {
+ return Flags.virtualCamera() && virtualCameraServiceDiscovery()
+ && nativeVirtualCameraServiceBuildFlagEnabled();
+ }
+
+ // Returns true if virtual_camera service is enabled in this build.
+ @FastNative
+ private static native boolean nativeVirtualCameraServiceBuildFlagEnabled();
}
diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java
index c5c2b0b..c7a8369 100644
--- a/services/core/java/com/android/server/PinnerService.java
+++ b/services/core/java/com/android/server/PinnerService.java
@@ -29,7 +29,6 @@
import android.app.ActivityManager;
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;
@@ -53,7 +52,6 @@
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;
@@ -139,7 +137,6 @@
private final ActivityManagerInternal mAmInternal;
private final IActivityManager mAm;
private final UserManager mUserManager;
- private SearchManager mSearchManager;
/** The list of the statically pinned files. */
@GuardedBy("this") private final ArrayMap<String, PinnedFile> mPinnedFiles = new ArrayMap<>();
@@ -283,15 +280,6 @@
sendPinAppsMessage(UserHandle.USER_SYSTEM);
}
- @Override
- public void onBootPhase(int phase) {
- // SearchManagerService is started after PinnerService, wait for PHASE_SYSTEM_SERVICES_READY
- if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
- mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
- sendPinAppsMessage(UserHandle.USER_SYSTEM);
- }
- }
-
/**
* Repin apps on user switch.
* <p>
@@ -308,8 +296,9 @@
@Override
public void onUserUnlocking(@NonNull TargetUser user) {
- int userId = user.getUserIdentifier();
- if (!mUserManager.isManagedProfile(userId)) {
+ final int userId = user.getUserIdentifier();
+ if (userId != UserHandle.USER_SYSTEM && !mUserManager.isManagedProfile(userId)) {
+ // App pinning for the system should have already been triggered from onStart().
sendPinAppsMessage(userId);
}
}
@@ -532,11 +521,8 @@
}
private ApplicationInfo getAssistantInfo(int userHandle) {
- if (mSearchManager != null) {
- Intent intent = mSearchManager.getAssistIntent(false);
- return getApplicationInfoForIntent(intent, userHandle, true);
- }
- return null;
+ Intent intent = new Intent(Intent.ACTION_ASSIST);
+ return getApplicationInfoForIntent(intent, userHandle, true);
}
private ApplicationInfo getApplicationInfoForIntent(Intent intent, int userHandle,
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index 7d8aad7..ecd14ce 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -24,6 +24,7 @@
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionManager;
@@ -79,7 +80,7 @@
Trace.beginSection(
"SensitiveContentProtectionManagerService.onProjectionStart");
try {
- onProjectionStart(info);
+ onProjectionStart(info.getPackageName());
} finally {
Trace.endSection();
}
@@ -124,14 +125,6 @@
}
}
- // These packages are exempted from screen share protection.
- private ArraySet<String> getExemptedPackages() {
- final ArraySet<String> exemptedPackages =
- SystemConfig.getInstance().getBugreportWhitelistedPackages();
- // TODO(b/323361046) - Add sys ui recorder package.
- return exemptedPackages;
- }
-
@VisibleForTesting
void init(MediaProjectionManager projectionManager, WindowManagerInternal windowManager,
ArraySet<String> exemptedPackages) {
@@ -179,9 +172,22 @@
}
}
- private void onProjectionStart(MediaProjectionInfo info) {
- if (mExemptedPackages != null && mExemptedPackages.contains(info.getPackageName())) {
- Log.w(TAG, info.getPackageName() + " is exempted from screen share protection.");
+ private boolean canRecordSensitiveContent(@NonNull String packageName) {
+ return getContext().getPackageManager()
+ .checkPermission(android.Manifest.permission.RECORD_SENSITIVE_CONTENT,
+ packageName) == PackageManager.PERMISSION_GRANTED;
+ }
+
+ // These packages are exempted from screen share protection.
+ private ArraySet<String> getExemptedPackages() {
+ return SystemConfig.getInstance().getBugreportWhitelistedPackages();
+ }
+
+ private void onProjectionStart(String packageName) {
+ // exempt on device screen recorder as well.
+ if ((mExemptedPackages != null && mExemptedPackages.contains(packageName))
+ || canRecordSensitiveContent(packageName)) {
+ Log.w(TAG, packageName + " is exempted from screen share protection.");
return;
}
// TODO(b/324447419): move GlobalSettings lookup to background thread
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 130a733..1334a95 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -113,6 +113,7 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
+import com.android.modules.expresslog.Histogram;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.SystemService;
@@ -284,6 +285,11 @@
private static AtomicReference<AccountManagerService> sThis = new AtomicReference<>();
private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[]{};
+ private static Histogram sResponseLatency = new Histogram(
+ "app.value_high_authenticator_response_latency",
+ new Histogram.ScaledRangeOptions(20, 10000, 10000, 1.5f)
+ );
+
/**
* This should only be called by system code. One should only call this after the service
* has started.
@@ -4937,6 +4943,9 @@
protected boolean mCanStartAccountManagerActivity = false;
protected final UserAccounts mAccounts;
+ private int mAuthenticatorUid;
+ private long mBindingStartTime;
+
public Session(UserAccounts accounts, IAccountManagerResponse response, String accountType,
boolean expectActivityLaunch, boolean stripAuthTokenFromResult, String accountName,
boolean authDetailsRequired) {
@@ -4974,6 +4983,10 @@
}
IAccountManagerResponse getResponseAndClose() {
+ if (mAuthenticatorUid != 0 && mBindingStartTime > 0) {
+ sResponseLatency.logSampleWithUid(mAuthenticatorUid,
+ SystemClock.uptimeMillis() - mBindingStartTime);
+ }
if (mResponse == null) {
close();
return null;
@@ -5353,7 +5366,8 @@
mContext.unbindService(this);
return false;
}
-
+ mAuthenticatorUid = authenticatorInfo.uid;
+ mBindingStartTime = SystemClock.uptimeMillis();
return true;
}
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 258f53d..0012b3d 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -119,6 +119,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOREGROUND_SERVICE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE_EXECUTING;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
@@ -203,6 +204,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
import android.service.voice.HotwordDetectionService;
import android.service.voice.VisualQueryDetectionService;
import android.service.wearable.WearableSensingService;
@@ -1497,6 +1499,11 @@
FrameworkStatsLog.write(FrameworkStatsLog.SERVICE_STATE_CHANGED, uid, packageName,
serviceName, FrameworkStatsLog.SERVICE_STATE_CHANGED__STATE__START);
mAm.mBatteryStatsService.noteServiceStartRunning(uid, packageName, serviceName);
+ final ProcessRecord hostApp = r.app;
+ final boolean wasStopped = hostApp == null ? wasStopped(r) : false;
+ final boolean firstLaunch =
+ hostApp == null ? !mAm.wasPackageEverLaunched(r.packageName, r.userId) : false;
+
String error = bringUpServiceLocked(r, service.getFlags(), callerFg,
false /* whileRestarting */,
false /* permissionsReviewRequired */,
@@ -1509,10 +1516,14 @@
return new ComponentName("!!", error);
}
- final boolean wasStopped = (r.appInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0;
final int packageState = wasStopped
? SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED
: SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
+ if (DEBUG_PROCESSES) {
+ Slog.d(TAG, "Logging startService for " + packageName + ", stopped="
+ + wasStopped + ", firstLaunch=" + firstLaunch + ", intent=" + service
+ + ", r.app=" + r.app);
+ }
FrameworkStatsLog.write(SERVICE_REQUEST_EVENT_REPORTED, uid, callingUid,
service.getAction(),
SERVICE_REQUEST_EVENT_REPORTED__REQUEST_TYPE__START, false,
@@ -1527,7 +1538,9 @@
packageName,
callingPackage,
callingProcessState,
- r.mProcessStateOnRequest);
+ r.mProcessStateOnRequest,
+ firstLaunch,
+ 0L /* TODO: stoppedDuration */);
if (r.startRequested && addToStarting) {
boolean first = smap.mStartingBackground.size() == 0;
@@ -4038,7 +4051,6 @@
mAm.requireAllowedAssociationsLocked(s.appInfo.packageName);
}
- final boolean wasStopped = (s.appInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0;
final boolean wasStartRequested = s.startRequested;
final boolean hadConnections = !s.getConnections().isEmpty();
mAm.startAssociationLocked(callerApp.uid, callerApp.processName,
@@ -4113,6 +4125,10 @@
true);
}
+ final boolean wasStopped = hostApp == null ? wasStopped(s) : false;
+ final boolean firstLaunch =
+ hostApp == null ? !mAm.wasPackageEverLaunched(s.packageName, s.userId) : false;
+
boolean needOomAdj = false;
if (c.hasFlag(Context.BIND_AUTO_CREATE)) {
s.lastActivity = SystemClock.uptimeMillis();
@@ -4155,6 +4171,10 @@
final int packageState = wasStopped
? SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED
: SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
+ if (DEBUG_PROCESSES) {
+ Slog.d(TAG, "Logging bindService for " + s.packageName
+ + ", stopped=" + wasStopped + ", firstLaunch=" + firstLaunch);
+ }
FrameworkStatsLog.write(SERVICE_REQUEST_EVENT_REPORTED, s.appInfo.uid, callingUid,
ActivityManagerService.getShortAction(service.getAction()),
SERVICE_REQUEST_EVENT_REPORTED__REQUEST_TYPE__BIND, false,
@@ -4169,7 +4189,9 @@
s.packageName,
callerApp.info.packageName,
callerApp.mState.getCurProcState(),
- s.mProcessStateOnRequest);
+ s.mProcessStateOnRequest,
+ firstLaunch,
+ 0L /* TODO */);
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bind " + s + " with " + b
+ ": received=" + b.intent.received
@@ -4497,13 +4519,14 @@
}
// TODO(b/265746493): Special case for HotwordDetectionService,
- // VisualQueryDetectionService and WearableSensingService.
+ // VisualQueryDetectionService, WearableSensingService and OnDeviceSandboxedInferenceService
// Need a cleaner way to append this seInfo.
private String generateAdditionalSeInfoFromService(Intent service) {
if (service != null && service.getAction() != null
&& (service.getAction().equals(HotwordDetectionService.SERVICE_INTERFACE)
|| service.getAction().equals(VisualQueryDetectionService.SERVICE_INTERFACE)
- || service.getAction().equals(WearableSensingService.SERVICE_INTERFACE))) {
+ || service.getAction().equals(WearableSensingService.SERVICE_INTERFACE)
+ || service.getAction().equals(OnDeviceSandboxedInferenceService.SERVICE_INTERFACE))) {
return ":isolatedComputeApp";
}
return "";
@@ -9112,4 +9135,8 @@
return mCachedDeviceProvisioningPackage != null
&& mCachedDeviceProvisioningPackage.equals(packageName);
}
+
+ private boolean wasStopped(ServiceRecord serviceRecord) {
+ return (serviceRecord.appInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0;
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5e36709..447dfd9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -725,12 +725,6 @@
// Whether we should use SCHED_FIFO for UI and RenderThreads.
final boolean mUseFifoUiScheduling;
- /**
- * Flag indicating if we should use {@link BroadcastQueueModernImpl} instead
- * of the default {@link BroadcastQueueImpl}.
- */
- final boolean mEnableModernQueue;
-
@GuardedBy("this")
private final SparseArray<IUnsafeIntentStrictModeCallback>
mStrictModeCallbacks = new SparseArray<>();
@@ -2508,7 +2502,6 @@
mInternal = new LocalService();
mPendingStartActivityUids = new PendingStartActivityUids();
mUseFifoUiScheduling = false;
- mEnableModernQueue = false;
mBroadcastQueue = injector.getBroadcastQueue(this);
mComponentAliasResolver = new ComponentAliasResolver(this);
}
@@ -2550,9 +2543,6 @@
? new OomAdjusterModernImpl(this, mProcessList, activeUids)
: new OomAdjuster(this, mProcessList, activeUids);
- mEnableModernQueue = new BroadcastConstants(
- Settings.Global.BROADCAST_FG_CONSTANTS).MODERN_QUEUE_ENABLED;
-
mBroadcastQueue = mInjector.getBroadcastQueue(this);
mServices = new ActiveServices(this);
@@ -4765,6 +4755,7 @@
autofillOptions,
contentCaptureOptions,
app.getDisabledCompatChanges(),
+ app.getLoggableCompatChanges(),
serializedSystemFontMap,
app.getStartElapsedTime(),
app.getStartUptime());
@@ -5046,8 +5037,11 @@
* Send LOCKED_BOOT_COMPLETED and BOOT_COMPLETED to the package explicitly when unstopped
*/
private void maybeSendBootCompletedLocked(ProcessRecord app) {
+ if (!android.content.pm.Flags.stayStopped()) return;
// Nothing to do if it wasn't previously stopped
- if (!android.content.pm.Flags.stayStopped() || !app.wasForceStopped()) return;
+ if (!app.wasForceStopped() && !app.getWindowProcessController().wasForceStopped()) {
+ return;
+ }
// Send LOCKED_BOOT_COMPLETED, if necessary
if (app.getApplicationInfo().isEncryptionAware()) {
@@ -5059,7 +5053,8 @@
sendBootBroadcastToAppLocked(app, new Intent(Intent.ACTION_BOOT_COMPLETED),
REASON_BOOT_COMPLETED);
}
- app.setWasForceStopped(false);
+ // The stopped state is reset in ProcessRecord when the pid changes, to deal with
+ // any re-use of the ProcessRecord.
}
/** Send a boot_completed broadcast to app */
@@ -6844,6 +6839,17 @@
return mPermissionManagerInt;
}
+ /** Returns whether the given package was ever launched since install */
+ boolean wasPackageEverLaunched(String packageName, @UserIdInt int userId) {
+ boolean wasLaunched = false;
+ try {
+ wasLaunched = getPackageManagerInternal().wasPackageEverLaunched(packageName, userId);
+ } catch (Exception e) {
+ // If the package state record doesn't exist yet, assume it was never launched
+ }
+ return wasLaunched;
+ }
+
private TestUtilityService getTestUtilityServiceLocked() {
if (mTestUtilityService == null) {
mTestUtilityService =
@@ -9005,7 +9011,7 @@
// cleaning up the old proxies.
VMRuntime.getRuntime().requestConcurrentGC();
}
- }, BackgroundThread.getHandler());
+ }, mHandler);
t.traceEnd(); // setBinderProxies
t.traceEnd(); // ActivityManagerStartApps
@@ -15010,9 +15016,6 @@
+ " ordered=" + ordered + " userid=" + userId
+ " options=" + (brOptions == null ? "null" : brOptions.toBundle()));
if ((resultTo != null) && !ordered) {
- if (!mEnableModernQueue) {
- Slog.w(TAG, "Broadcast " + intent + " not ordered but result callback requested!");
- }
if (!UserHandle.isCore(callingUid)) {
String msg = "Unauthorized unordered resultTo broadcast "
+ intent + " sent from uid " + callingUid;
@@ -15613,29 +15616,6 @@
filterNonExportedComponents(intent, callingUid, callingPid, registeredReceivers,
mPlatformCompat, callerPackage, resolvedType);
int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
- if (!ordered && NR > 0 && !mEnableModernQueue) {
- // If we are not serializing this broadcast, then send the
- // registered receivers separately so they don't wait for the
- // components to be launched. We don't do this split for the modern
- // queue because delivery to registered receivers isn't blocked
- // behind manifest receivers.
- if (isCallerSystem) {
- checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,
- isProtectedBroadcast, registeredReceivers);
- }
- final BroadcastQueue queue = mBroadcastQueue;
- BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
- callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
- requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
- registeredReceivers, resultToApp, resultTo, resultCode, resultData,
- resultExtras, ordered, sticky, false, userId,
- backgroundStartPrivileges, timeoutExempt, filterExtrasForReceiver,
- callerAppProcessState);
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing parallel broadcast " + r);
- queue.enqueueBroadcastLocked(r);
- registeredReceivers = null;
- NR = 0;
- }
// Merge into one list.
int ir = 0;
@@ -18287,11 +18267,6 @@
}
@Override
- public boolean isModernQueueEnabled() {
- return mEnableModernQueue;
- }
-
- @Override
public void enforceBroadcastOptionsPermissions(Bundle options, int callingUid) {
enforceBroadcastOptionPermissionsInternal(options, callingUid);
}
@@ -18720,7 +18695,6 @@
Binder.restoreCallingIdentity(origId);
}
}
-
}
@Override
@@ -18730,12 +18704,8 @@
int userId, int[] appIdAllowList,
@Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
@Nullable Bundle bOptions) {
- // Sending broadcasts with a finish callback without the need for the broadcasts
- // delivery to be serialized is only supported by modern queue. So, when modern
- // queue is disabled, we continue to send broadcasts in a serialized fashion.
- final boolean serialized = !isModernQueueEnabled();
- return broadcastIntent(intent, resultTo, requiredPermissions, serialized, userId,
- appIdAllowList, filterExtrasForReceiver, bOptions);
+ return broadcastIntent(intent, resultTo, requiredPermissions, false /* serialized */,
+ userId, appIdAllowList, filterExtrasForReceiver, bOptions);
}
@Override
@@ -19774,21 +19744,11 @@
Objects.requireNonNull(targetPackage);
Preconditions.checkArgumentNonnegative(delayedDurationMs);
enforceCallingPermission(permission.DUMP, "forceDelayBroadcastDelivery()");
- // Ignore request if modern queue is not enabled
- if (!mEnableModernQueue) {
- return;
- }
mBroadcastQueue.forceDelayBroadcastDelivery(targetPackage, delayedDurationMs);
}
@Override
- public boolean isModernBroadcastQueueEnabled() {
- enforceCallingPermission(permission.DUMP, "isModernBroadcastQueueEnabled()");
- return mEnableModernQueue;
- }
-
- @Override
public boolean isProcessFrozen(int pid) {
enforceCallingPermission(permission.DUMP, "isProcessFrozen()");
return mOomAdjuster.mCachedAppOptimizer.isProcessFrozen(pid);
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 45f657d..4ebabdc 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -1164,7 +1164,8 @@
synchronized (mInternal) {
synchronized (mInternal.mProcLock) {
app.mOptRecord.setFreezeSticky(isSticky);
- mInternal.mOomAdjuster.mCachedAppOptimizer.freezeAppAsyncInternalLSP(app, 0, true);
+ mInternal.mOomAdjuster.mCachedAppOptimizer.freezeAppAsyncInternalLSP(
+ app, 0 /* delayMillis */, true /* force */, false /* immediate */);
}
}
return 0;
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 0ce1407..48daef8 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -711,7 +711,7 @@
}
}
if (profile != null) {
- long startTime = SystemClock.currentThreadTimeMillis();
+ long startTime = SystemClock.uptimeMillis();
// skip background PSS calculation under the following situations:
// - app is capturing camera imagery
// - app is frozen and we have already collected PSS once.
@@ -721,7 +721,7 @@
|| mService.isCameraActiveForUid(profile.mApp.uid)
|| mService.mConstants.APP_PROFILER_PSS_PROFILING_DISABLED;
long pss = skipPSSCollection ? 0 : Debug.getPss(pid, tmp, null);
- long endTime = SystemClock.currentThreadTimeMillis();
+ long endTime = SystemClock.uptimeMillis();
synchronized (mProfilerLock) {
if (pss != 0 && profile.getThread() != null
&& profile.getSetProcState() == procState
@@ -852,7 +852,7 @@
}
}
if (profile != null) {
- long startTime = SystemClock.currentThreadTimeMillis();
+ long startTime = SystemClock.uptimeMillis();
// skip background RSS calculation under the following situations:
// - app is capturing camera imagery
// - app is frozen and we have already collected RSS once.
@@ -862,7 +862,7 @@
|| mService.isCameraActiveForUid(profile.mApp.uid)
|| mService.mConstants.APP_PROFILER_PSS_PROFILING_DISABLED;
long rss = skipRSSCollection ? 0 : Debug.getRss(pid, null);
- long endTime = SystemClock.currentThreadTimeMillis();
+ long endTime = SystemClock.uptimeMillis();
synchronized (mProfilerLock) {
if (rss != 0 && profile.getThread() != null
&& profile.getSetProcState() == procState
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index 1dc384d..3e633cc 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -54,6 +54,7 @@
import com.android.server.IoThread;
import com.android.server.ServiceThread;
import com.android.server.SystemServiceManager;
+import com.android.server.wm.WindowProcessController;
import java.io.File;
import java.io.FileInputStream;
@@ -385,8 +386,10 @@
start.setPackageName(app.info.packageName);
if (android.content.pm.Flags.stayStopped()) {
// TODO: Verify this is created at the right time to have the correct force-stopped
- // state in the ProcessRecord. Also use the WindowProcessRecord if activity.
- start.setForceStopped(app.wasForceStopped());
+ // state in the ProcessRecord.
+ final WindowProcessController wpc = app.getWindowProcessController();
+ start.setForceStopped(app.wasForceStopped()
+ || (wpc != null ? wpc.wasForceStopped() : false));
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 2fff79b..57080f8 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -128,14 +128,6 @@
public long ALLOW_BG_ACTIVITY_START_TIMEOUT = DEFAULT_ALLOW_BG_ACTIVITY_START_TIMEOUT;
/**
- * Flag indicating if we should use {@link BroadcastQueueModernImpl} instead
- * of the default {@link BroadcastQueueImpl}.
- */
- public boolean MODERN_QUEUE_ENABLED = DEFAULT_MODERN_QUEUE_ENABLED;
- private static final String KEY_MODERN_QUEUE_ENABLED = "modern_queue_enabled";
- private static final boolean DEFAULT_MODERN_QUEUE_ENABLED = true;
-
- /**
* For {@link BroadcastQueueModernImpl}: Maximum dispatch parallelism
* that we'll tolerate for ordinary broadcast dispatch.
*/
@@ -296,11 +288,21 @@
* For {@link BroadcastQueueModernImpl}: How frequently we should check for the pending
* cold start validity.
*/
- public long PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 30 * 1000;
+ public long PENDING_COLD_START_CHECK_INTERVAL_MILLIS =
+ DEFAULT_PENDING_COLD_START_CHECK_INTERVAL_MILLIS;
private static final String KEY_PENDING_COLD_START_CHECK_INTERVAL_MILLIS =
"pending_cold_start_check_interval_millis";
private static final long DEFAULT_PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 30_000;
+ /**
+ * For {@link BroadcastQueueModernImpl}: Maximum number of outgoing broadcasts from a
+ * freezable process that will be allowed before killing the process.
+ */
+ public long MAX_FROZEN_OUTGOING_BROADCASTS = DEFAULT_MAX_FROZEN_OUTGOING_BROADCASTS;
+ private static final String KEY_MAX_FROZEN_OUTGOING_BROADCASTS =
+ "max_frozen_outgoing_broadcasts";
+ private static final int DEFAULT_MAX_FROZEN_OUTGOING_BROADCASTS = 32;
+
// Settings override tracking for this instance
private String mSettingsKey;
private SettingsObserver mSettingsObserver;
@@ -411,8 +413,6 @@
*/
private void updateDeviceConfigConstants() {
synchronized (this) {
- MODERN_QUEUE_ENABLED = getDeviceConfigBoolean(KEY_MODERN_QUEUE_ENABLED,
- DEFAULT_MODERN_QUEUE_ENABLED);
MAX_RUNNING_PROCESS_QUEUES = getDeviceConfigInt(KEY_MAX_RUNNING_PROCESS_QUEUES,
DEFAULT_MAX_RUNNING_PROCESS_QUEUES);
EXTRA_RUNNING_URGENT_PROCESS_QUEUES = getDeviceConfigInt(
@@ -453,6 +453,9 @@
PENDING_COLD_START_CHECK_INTERVAL_MILLIS = getDeviceConfigLong(
KEY_PENDING_COLD_START_CHECK_INTERVAL_MILLIS,
DEFAULT_PENDING_COLD_START_CHECK_INTERVAL_MILLIS);
+ MAX_FROZEN_OUTGOING_BROADCASTS = getDeviceConfigInt(
+ KEY_MAX_FROZEN_OUTGOING_BROADCASTS,
+ DEFAULT_MAX_FROZEN_OUTGOING_BROADCASTS);
}
// TODO: migrate BroadcastRecord to accept a BroadcastConstants
@@ -485,7 +488,6 @@
pw.print(NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT);
pw.println("):");
pw.increaseIndent();
- pw.print(KEY_MODERN_QUEUE_ENABLED, MODERN_QUEUE_ENABLED).println();
pw.print(KEY_MAX_RUNNING_PROCESS_QUEUES, MAX_RUNNING_PROCESS_QUEUES).println();
pw.print(KEY_MAX_RUNNING_ACTIVE_BROADCASTS, MAX_RUNNING_ACTIVE_BROADCASTS).println();
pw.print(KEY_CORE_MAX_RUNNING_BLOCKING_BROADCASTS,
@@ -513,6 +515,8 @@
CORE_DEFER_UNTIL_ACTIVE).println();
pw.print(KEY_PENDING_COLD_START_CHECK_INTERVAL_MILLIS,
PENDING_COLD_START_CHECK_INTERVAL_MILLIS).println();
+ pw.print(KEY_MAX_FROZEN_OUTGOING_BROADCASTS,
+ MAX_FROZEN_OUTGOING_BROADCASTS).println();
pw.decreaseIndent();
pw.println();
}
diff --git a/services/core/java/com/android/server/am/BroadcastHistory.java b/services/core/java/com/android/server/am/BroadcastHistory.java
index 34658ca..d6e3d43 100644
--- a/services/core/java/com/android/server/am/BroadcastHistory.java
+++ b/services/core/java/com/android/server/am/BroadcastHistory.java
@@ -50,6 +50,11 @@
}
/**
+ * List of broadcasts in frozen processes that are yet to be enqueued.
+ */
+ private final ArrayList<BroadcastRecord> mFrozenBroadcasts = new ArrayList<>();
+
+ /**
* List of broadcasts which are being delivered or yet to be delivered.
*/
private final ArrayList<BroadcastRecord> mPendingBroadcasts = new ArrayList<>();
@@ -77,7 +82,12 @@
final long[] mSummaryHistoryDispatchTime;
final long[] mSummaryHistoryFinishTime;
+ void onBroadcastFrozenLocked(@NonNull BroadcastRecord r) {
+ mFrozenBroadcasts.add(r);
+ }
+
void onBroadcastEnqueuedLocked(@NonNull BroadcastRecord r) {
+ mFrozenBroadcasts.remove(r);
mPendingBroadcasts.add(r);
}
@@ -101,7 +111,7 @@
mSummaryHistoryNext = ringAdvance(mSummaryHistoryNext, 1, MAX_BROADCAST_SUMMARY_HISTORY);
}
- private final int ringAdvance(int x, final int increment, final int ringSize) {
+ private int ringAdvance(int x, final int increment, final int ringSize) {
x += increment;
if (x < 0) return (ringSize - 1);
else if (x >= ringSize) return 0;
@@ -114,6 +124,10 @@
final BroadcastRecord r = mPendingBroadcasts.get(i);
r.dumpDebug(proto, BroadcastQueueProto.PENDING_BROADCASTS);
}
+ for (int i = 0; i < mFrozenBroadcasts.size(); ++i) {
+ final BroadcastRecord r = mFrozenBroadcasts.get(i);
+ r.dumpDebug(proto, BroadcastQueueProto.FROZEN_BROADCASTS);
+ }
int lastIndex = mHistoryNext;
int ringIndex = lastIndex;
@@ -151,16 +165,8 @@
public boolean dumpLocked(@NonNull PrintWriter pw, @Nullable String dumpPackage,
@NonNull String queueName, @NonNull SimpleDateFormat sdf,
boolean dumpAll, boolean needSep) {
- pw.println(" Pending broadcasts:");
- if (mPendingBroadcasts.isEmpty()) {
- pw.println(" <empty>");
- } else {
- for (int idx = mPendingBroadcasts.size() - 1; idx >= 0; --idx) {
- final BroadcastRecord r = mPendingBroadcasts.get(idx);
- pw.print(" Broadcast #"); pw.print(idx); pw.println(":");
- r.dump(pw, " ", sdf);
- }
- }
+ dumpBroadcastList(pw, sdf, mFrozenBroadcasts, "Frozen");
+ dumpBroadcastList(pw, sdf, mPendingBroadcasts, "Pending");
int i;
boolean printed = false;
@@ -268,4 +274,18 @@
}
return needSep;
}
+
+ private void dumpBroadcastList(@NonNull PrintWriter pw, @NonNull SimpleDateFormat sdf,
+ @NonNull ArrayList<BroadcastRecord> broadcasts, @NonNull String flavor) {
+ pw.print(" "); pw.print(flavor); pw.println(" broadcasts:");
+ if (broadcasts.isEmpty()) {
+ pw.println(" <empty>");
+ } else {
+ for (int idx = broadcasts.size() - 1; idx >= 0; --idx) {
+ final BroadcastRecord r = broadcasts.get(idx);
+ pw.print(flavor); pw.print(" broadcast #"); pw.print(idx); pw.println(":");
+ r.dump(pw, " ", sdf);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 4a37913..e98e1ba 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -44,6 +44,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayDeque;
+import java.util.ArrayList;
import java.util.Iterator;
import java.util.Objects;
@@ -181,6 +182,12 @@
private boolean mActiveWasStopped;
/**
+ * Flag indicating that the currently active broadcast is being dispatched
+ * to a package that was never launched before.
+ */
+ private boolean mActiveFirstLaunch;
+
+ /**
* Number of consecutive urgent broadcasts that have been dispatched
* since the last non-urgent dispatch.
*/
@@ -233,6 +240,11 @@
*/
private long mForcedDelayedDurationMs;
+ /**
+ * List of outgoing broadcasts from a freezable process.
+ */
+ private final ArrayList<BroadcastRecord> mOutgoingBroadcasts = new ArrayList<>();
+
public BroadcastProcessQueue(@NonNull BroadcastConstants constants,
@NonNull String processName, int uid) {
this.constants = Objects.requireNonNull(constants);
@@ -250,6 +262,21 @@
}
}
+ public void enqueueOutgoingBroadcast(@NonNull BroadcastRecord record) {
+ mOutgoingBroadcasts.add(record);
+ }
+
+ public int getOutgoingBroadcastCount() {
+ return mOutgoingBroadcasts.size();
+ }
+
+ public void enqueueOutgoingBroadcasts(@NonNull BroadcastRecordConsumer consumer) {
+ for (int i = 0; i < mOutgoingBroadcasts.size(); ++i) {
+ consumer.accept(mOutgoingBroadcasts.get(i));
+ }
+ mOutgoingBroadcasts.clear();
+ }
+
/**
* Enqueue the given broadcast to be dispatched to this process at some
* future point in time. The target receiver is indicated by the given index
@@ -386,8 +413,8 @@
}
/**
- * Functional interface that tests a {@link BroadcastRecord} that has been
- * previously enqueued in {@link BroadcastProcessQueue}.
+ * Functional interface that tests a {@link BroadcastRecord} and an index in the
+ * {@link BroadcastRecord} that has been previously enqueued in {@link BroadcastProcessQueue}.
*/
@FunctionalInterface
public interface BroadcastPredicate {
@@ -395,8 +422,8 @@
}
/**
- * Functional interface that consumes a {@link BroadcastRecord} that has
- * been previously enqueued in {@link BroadcastProcessQueue}.
+ * Functional interface that consumes a {@link BroadcastRecord} and an index in the
+ * {@link BroadcastRecord} that has been previously enqueued in {@link BroadcastProcessQueue}.
*/
@FunctionalInterface
public interface BroadcastConsumer {
@@ -404,6 +431,15 @@
}
/**
+ * Functional interface that consumes a {@link BroadcastRecord} that has
+ * been previously enqueued in {@link BroadcastProcessQueue}.
+ */
+ @FunctionalInterface
+ public interface BroadcastRecordConsumer {
+ void accept(@NonNull BroadcastRecord r);
+ }
+
+ /**
* Invoke given consumer for any broadcasts matching given predicate. If
* requested, matching broadcasts will also be removed from this queue.
* <p>
@@ -596,6 +632,10 @@
mActiveWasStopped = activeWasStopped;
}
+ public void setActiveFirstLaunch(boolean activeFirstLaunch) {
+ mActiveFirstLaunch = activeFirstLaunch;
+ }
+
public boolean getActiveViaColdStart() {
return mActiveViaColdStart;
}
@@ -604,6 +644,10 @@
return mActiveWasStopped;
}
+ public boolean getActiveFirstLaunch() {
+ return mActiveFirstLaunch;
+ }
+
/**
* Get package name of the first application loaded into this process.
*/
@@ -774,6 +818,10 @@
return mActiveIndex;
}
+ public boolean isOutgoingEmpty() {
+ return mOutgoingBroadcasts.isEmpty();
+ }
+
public boolean isEmpty() {
return mPending.isEmpty() && mPendingUrgent.isEmpty() && mPendingOffload.isEmpty();
}
@@ -1443,7 +1491,7 @@
@NeverCompile
public void dumpLocked(@UptimeMillisLong long now, @NonNull IndentingPrintWriter pw) {
- if ((mActive == null) && isEmpty()) return;
+ if ((mActive == null) && isEmpty() && isOutgoingEmpty()) return;
pw.print(toShortString());
pw.print(" ");
@@ -1454,6 +1502,12 @@
dumpProcessState(pw);
dumpBroadcastCounts(pw);
+ if (!mOutgoingBroadcasts.isEmpty()) {
+ for (int i = 0; i < mOutgoingBroadcasts.size(); ++i) {
+ dumpOutgoingRecord(now, pw, mOutgoingBroadcasts.get(i));
+ }
+ }
+
if (mActive != null) {
dumpRecord("ACTIVE", now, pw, mActive, mActiveIndex);
}
@@ -1525,6 +1579,15 @@
}
@NeverCompile
+ private void dumpOutgoingRecord(@UptimeMillisLong long now,
+ @NonNull IndentingPrintWriter pw, @NonNull BroadcastRecord record) {
+ pw.print("OUTGOING ");
+ TimeUtils.formatDuration(record.enqueueTime, now, pw);
+ pw.print(' ');
+ pw.println(record.toShortString());
+ }
+
+ @NeverCompile
private void dumpRecord(@Nullable String flavor, @UptimeMillisLong long now,
@NonNull IndentingPrintWriter pw, @NonNull BroadcastRecord record, int recordIndex) {
TimeUtils.formatDuration(record.enqueueTime, now, pw);
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 4422608..0a6e9d3 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -32,6 +32,7 @@
import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO;
import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList;
import static com.android.server.am.BroadcastProcessQueue.reasonToString;
@@ -92,6 +93,7 @@
import com.android.server.LocalServices;
import com.android.server.am.BroadcastProcessQueue.BroadcastConsumer;
import com.android.server.am.BroadcastProcessQueue.BroadcastPredicate;
+import com.android.server.am.BroadcastProcessQueue.BroadcastRecordConsumer;
import com.android.server.am.BroadcastRecord.DeliveryState;
import com.android.server.pm.UserJourneyLogger;
import com.android.server.pm.UserManagerInternal;
@@ -332,9 +334,7 @@
return true;
}
case MSG_PROCESS_FREEZABLE_CHANGED: {
- synchronized (mService) {
- refreshProcessQueueLocked((ProcessRecord) msg.obj);
- }
+ handleProcessFreezableChanged((ProcessRecord) msg.obj);
return true;
}
case MSG_UID_STATE_CHANGED: {
@@ -435,7 +435,8 @@
}
// If app isn't running, and there's nothing in the queue, clean up
- if (queue.isEmpty() && !queue.isActive() && !queue.isProcessWarm()) {
+ if (queue.isEmpty() && queue.isOutgoingEmpty() && !queue.isActive()
+ && !queue.isProcessWarm()) {
removeProcessQueue(queue.processName, queue.uid);
}
}
@@ -759,6 +760,21 @@
@Override
public void enqueueBroadcastLocked(@NonNull BroadcastRecord r) {
+ // TODO: Apply delivery group policies and FLAG_REPLACE_PENDING to collapse the
+ // outgoing broadcasts.
+ // TODO: Add traces/logs for the enqueueing outgoing broadcasts logic.
+ if (Flags.deferOutgoingBcasts() && isProcessFreezable(r.callerApp)) {
+ final BroadcastProcessQueue queue = getOrCreateProcessQueue(
+ r.callerApp.processName, r.callerApp.uid);
+ if (queue.getOutgoingBroadcastCount() >= mConstants.MAX_FROZEN_OUTGOING_BROADCASTS) {
+ // TODO: Kill the process if the outgoing broadcasts count is
+ // beyond a certain limit.
+ }
+ queue.enqueueOutgoingBroadcast(r);
+ mHistory.onBroadcastFrozenLocked(r);
+ mService.mOomAdjuster.mCachedAppOptimizer.freezeAppAsyncImmediateLSP(r.callerApp);
+ return;
+ }
if (DEBUG_BROADCAST) logv("Enqueuing " + r + " for " + r.receivers.size() + " receivers");
final int cookie = traceBegin("enqueueBroadcast");
@@ -966,6 +982,9 @@
queue.setActiveWasStopped(true);
}
final int intentFlags = r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND;
+ final boolean firstLaunch = !mService.wasPackageEverLaunched(info.packageName, r.userId);
+ queue.setActiveFirstLaunch(firstLaunch);
+
final HostingRecord hostingRecord = new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST,
component, r.intent.getAction(), r.getHostingRecordTriggerType());
final boolean isActivityCapable = (r.options != null
@@ -1634,6 +1653,8 @@
"mBroadcastConsumerDeferClear");
};
+ final BroadcastRecordConsumer mBroadcastRecordConsumerEnqueue = this::enqueueBroadcastLocked;
+
/**
* Verify that all known {@link #mProcessQueues} are in the state tested by
* the given {@link Predicate}.
@@ -1930,8 +1951,9 @@
}
}
+ @VisibleForTesting
@GuardedBy("mService")
- private boolean isProcessFreezable(@Nullable ProcessRecord app) {
+ boolean isProcessFreezable(@Nullable ProcessRecord app) {
if (app == null) {
return false;
}
@@ -1956,16 +1978,25 @@
enqueueUpdateRunningList();
}
+ private void handleProcessFreezableChanged(@NonNull ProcessRecord app) {
+ synchronized (mService) {
+ final BroadcastProcessQueue queue = getProcessQueue(app.processName, app.uid);
+ if (queue == null || queue.app == null || queue.app.getPid() != app.getPid()) {
+ return;
+ }
+ if (!isProcessFreezable(app)) {
+ queue.enqueueOutgoingBroadcasts(mBroadcastRecordConsumerEnqueue);
+ }
+ refreshProcessQueueLocked(queue);
+ }
+ }
+
/**
* Refresh the process queue corresponding to {@code app} with the latest process state
* so that runnableAt can be updated.
*/
@GuardedBy("mService")
- private void refreshProcessQueueLocked(@NonNull ProcessRecord app) {
- final BroadcastProcessQueue queue = getProcessQueue(app.processName, app.uid);
- if (queue == null || queue.app == null || queue.app.getPid() != app.getPid()) {
- return;
- }
+ private void refreshProcessQueueLocked(@NonNull BroadcastProcessQueue queue) {
setQueueProcess(queue, queue.app);
enqueueUpdateRunningList();
}
@@ -2108,6 +2139,12 @@
final long dispatchDelay = r.scheduledTime[index] - r.enqueueTime;
final long receiveDelay = 0;
final long finishDelay = r.terminalTime[index] - r.scheduledTime[index];
+ if (DEBUG_PROCESSES) {
+ Slog.d(TAG, "Logging broadcast for "
+ + (app != null ? app.info.packageName : "<null>")
+ + ", stopped=" + queue.getActiveWasStopped()
+ + ", firstLaunch=" + queue.getActiveFirstLaunch());
+ }
if (queue != null) {
final int packageState = queue.getActiveWasStopped()
? SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED
@@ -2117,7 +2154,11 @@
app != null ? app.info.packageName : null, r.callerPackage,
r.calculateTypeForLogging(), r.getDeliveryGroupPolicy(), r.intent.getFlags(),
BroadcastRecord.getReceiverPriority(receiver), r.callerProcState,
- receiverProcessState);
+ receiverProcessState, queue.getActiveFirstLaunch(),
+ 0L /* TODO: stoppedDuration */);
+ // Reset the states after logging
+ queue.setActiveFirstLaunch(false);
+ queue.setActiveWasStopped(false);
}
}
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 150f406..0cf5575 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -1415,7 +1415,7 @@
@GuardedBy({"mAm", "mProcLock"})
private void freezeAppAsyncLSP(ProcessRecord app, @UptimeMillisLong long delayMillis) {
- freezeAppAsyncInternalLSP(app, delayMillis, false);
+ freezeAppAsyncInternalLSP(app, delayMillis, false, false);
}
@GuardedBy({"mAm", "mProcLock"})
@@ -1423,11 +1423,25 @@
freezeAppAsyncLSP(app, updateEarliestFreezableTime(app, 0));
}
+ // TODO: Update freezeAppAsyncAtEarliestLSP to actually freeze the app at the earliest
+ // and remove this method.
+ @GuardedBy({"mAm", "mProcLock"})
+ void freezeAppAsyncImmediateLSP(ProcessRecord app) {
+ freezeAppAsyncInternalLSP(app, 0, false, true);
+ }
+
+ // TODO: Update this method to be private and have the existing clients call different methods.
+ // This "internal" method should not be directly triggered by clients outside this class.
@GuardedBy({"mAm", "mProcLock"})
void freezeAppAsyncInternalLSP(ProcessRecord app, @UptimeMillisLong long delayMillis,
- boolean force) {
+ boolean force, boolean immediate) {
final ProcessCachedOptimizerRecord opt = app.mOptRecord;
if (opt.isPendingFreeze()) {
+ if (immediate) {
+ mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);
+ mFreezeHandler.sendMessage(mFreezeHandler.obtainMessage(
+ SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app));
+ }
// Skip redundant DO_FREEZE message
return;
}
@@ -2210,6 +2224,9 @@
case SET_FROZEN_PROCESS_MSG: {
ProcessRecord proc = (ProcessRecord) msg.obj;
synchronized (mAm) {
+ if (!proc.mOptRecord.isPendingFreeze()) {
+ return;
+ }
freezeProcess(proc);
}
if (proc.mOptRecord.isFrozen()) {
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index cb7898d..f76bf37 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -34,6 +34,7 @@
import static com.android.internal.util.FrameworkStatsLog.PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
import static com.android.internal.util.FrameworkStatsLog.PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
import static com.android.server.am.ActivityManagerService.TAG_MU;
import static com.android.server.am.Flags.serviceBindingOomAdjPolicy;
@@ -290,7 +291,8 @@
PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
cpi.packageName, callingPackage,
- callingProcessState, callingProcessState);
+ callingProcessState, callingProcessState,
+ false, 0L);
return holder;
}
@@ -368,7 +370,7 @@
PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
cpi.packageName, callingPackage,
- callingProcessState, providerProcessState);
+ callingProcessState, providerProcessState, false, 0L);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -546,12 +548,16 @@
PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
cpi.packageName, callingPackage,
- callingProcessState, proc.mState.getCurProcState());
+ callingProcessState, proc.mState.getCurProcState(),
+ false, 0L);
} else {
- final int packageState =
- ((cpr.appInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0)
+ final boolean stopped =
+ (cpr.appInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0;
+ final int packageState = stopped
? PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED
: PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
+ final boolean firstLaunch = !mService.wasPackageEverLaunched(
+ cpi.packageName, userId);
checkTime(startTime, "getContentProviderImpl: before start process");
proc = mService.startProcessLocked(
cpi.processName, cpr.appInfo, false, 0,
@@ -567,12 +573,18 @@
+ ": process is bad");
return null;
}
+ if (DEBUG_PROCESSES) {
+ Slog.d(TAG, "Logging provider access for " + cpi.packageName
+ + ", stopped=" + stopped + ", firstLaunch=" + firstLaunch);
+ }
FrameworkStatsLog.write(
PROVIDER_ACQUISITION_EVENT_REPORTED,
proc.uid, callingUid,
PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD,
packageState, cpi.packageName, callingPackage,
- callingProcessState, ActivityManager.PROCESS_STATE_NONEXISTENT);
+ callingProcessState, ActivityManager.PROCESS_STATE_NONEXISTENT,
+ firstLaunch,
+ 0L /* TODO: stoppedDuration */);
}
cpr.launchingApp = proc;
mLaunchingProviders.add(cpr);
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 982076d..91b64f8 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -80,6 +80,7 @@
sSecureSettingToTypeMap.put(Settings.Secure.MULTI_PRESS_TIMEOUT, int.class);
sSecureSettingToTypeMap.put(Settings.Secure.KEY_REPEAT_TIMEOUT_MS, int.class);
sSecureSettingToTypeMap.put(Settings.Secure.KEY_REPEAT_DELAY_MS, int.class);
+ sSecureSettingToTypeMap.put(Settings.Secure.STYLUS_POINTER_ICON_ENABLED, int.class);
// add other secure settings here...
sSystemSettingToTypeMap.put(Settings.System.TIME_12_24, String.class);
diff --git a/services/core/java/com/android/server/am/HostingRecord.java b/services/core/java/com/android/server/am/HostingRecord.java
index 30811a1..1a78a13 100644
--- a/services/core/java/com/android/server/am/HostingRecord.java
+++ b/services/core/java/com/android/server/am/HostingRecord.java
@@ -325,4 +325,15 @@
return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_UNKNOWN;
}
}
+
+ private static boolean isTypeActivity(String hostingType) {
+ return HOSTING_TYPE_ACTIVITY.equals(hostingType)
+ || HOSTING_TYPE_NEXT_ACTIVITY.equals(hostingType)
+ || HOSTING_TYPE_NEXT_TOP_ACTIVITY.equals(hostingType)
+ || HOSTING_TYPE_TOP_ACTIVITY.equals(hostingType);
+ }
+
+ public boolean isTypeActivity() {
+ return isTypeActivity(mHostingType);
+ }
}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index cd6964e..7f6d62c 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1051,7 +1051,7 @@
assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
- postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime);
+ postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime, true);
if (startProfiling) {
mService.mOomAdjProfiler.oomAdjEnded();
@@ -1073,12 +1073,12 @@
@GuardedBy({"mService", "mProcLock"})
protected void postUpdateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, ActiveUids activeUids,
- long now, long nowElapsed, long oldTime) {
+ long now, long nowElapsed, long oldTime, boolean doingAll) {
mNumNonCachedProcs = 0;
mNumCachedHiddenProcs = 0;
final boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids,
- oomAdjReason);
+ oomAdjReason, doingAll);
mNumServiceProcs = mNewNumServiceProcs;
if (mService.mAlwaysFinishActivities) {
@@ -1288,7 +1288,8 @@
@GuardedBy({"mService", "mProcLock"})
private boolean updateAndTrimProcessLSP(final long now, final long nowElapsed,
- final long oldTime, final ActiveUids activeUids, @OomAdjReason int oomAdjReason) {
+ final long oldTime, final ActiveUids activeUids, @OomAdjReason int oomAdjReason,
+ boolean doingAll) {
ArrayList<ProcessRecord> lruList = mProcessList.getLruProcessesLOSP();
final int numLru = lruList.size();
@@ -1321,7 +1322,7 @@
if (!app.isKilledByAm() && app.getThread() != null) {
// We don't need to apply the update for the process which didn't get computed
if (state.getCompletedAdjSeq() == mAdjSeq) {
- applyOomAdjLSP(app, true, now, nowElapsed, oomAdjReason);
+ applyOomAdjLSP(app, doingAll, now, nowElapsed, oomAdjReason);
}
if (app.isPendingFinishAttach()) {
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index dd75bc0..46bdfe8 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -820,7 +820,7 @@
computeConnectionsLSP();
assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
- postUpdateOomAdjInnerLSP(oomAdjReason, mActiveUids, now, nowElapsed, oldTime);
+ postUpdateOomAdjInnerLSP(oomAdjReason, mActiveUids, now, nowElapsed, oldTime, true);
}
/**
@@ -908,7 +908,7 @@
}
}
- postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime);
+ postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime, false);
}
/**
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index a1fdd50..48a9d6a 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -59,6 +59,8 @@
import static com.android.server.am.ActivityManagerService.TAG_NETWORK;
import static com.android.server.am.ActivityManagerService.TAG_PROCESSES;
import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS;
+import static com.android.server.wm.WindowProcessController.STOPPED_STATE_FIRST_LAUNCH;
+import static com.android.server.wm.WindowProcessController.STOPPED_STATE_FORCE_STOPPED;
import android.Manifest;
import android.annotation.NonNull;
@@ -2086,8 +2088,10 @@
+ " with non-zero pid:" + app.getPid());
}
app.setDisabledCompatChanges(null);
+ app.setLoggableCompatChanges(null);
if (mPlatformCompat != null) {
app.setDisabledCompatChanges(mPlatformCompat.getDisabledChanges(app.info));
+ app.setLoggableCompatChanges(mPlatformCompat.getLoggableChanges(app.info));
}
final long startSeq = ++mProcStartSeqCounter;
app.setStartSeq(startSeq);
@@ -3327,19 +3331,24 @@
hostingRecord.getDefiningUid(), hostingRecord.getDefiningProcessName());
final ProcessStateRecord state = r.mState;
+ final boolean wasStopped = (info.flags & ApplicationInfo.FLAG_STOPPED) != 0;
// Check if we should mark the processrecord for first launch after force-stopping
- if ((r.getApplicationInfo().flags & ApplicationInfo.FLAG_STOPPED) != 0) {
- try {
- final boolean wasPackageEverLaunched = mService.getPackageManagerInternal()
+ if (wasStopped) {
+ // Check if the hosting record is for an activity or not. Since the stopped
+ // state tracking is handled differently to avoid WM calling back into AM,
+ // store the state in the correct record
+ if (hostingRecord.isTypeActivity()) {
+ final boolean wasPackageEverLaunched = mService
.wasPackageEverLaunched(r.getApplicationInfo().packageName, r.userId);
- // If the package was launched in the past but is currently stopped, only then it
- // should be considered as stopped after use. Do not mark it if it's the
- // first launch.
- if (wasPackageEverLaunched) {
- r.setWasForceStopped(true);
- }
- } catch (IllegalArgumentException e) {
- // App doesn't have state yet, so wasn't forcestopped
+ // If the package was launched in the past but is currently stopped, only then
+ // should it be considered as force-stopped.
+ @WindowProcessController.StoppedState int stoppedState = wasPackageEverLaunched
+ ? STOPPED_STATE_FORCE_STOPPED
+ : STOPPED_STATE_FIRST_LAUNCH;
+ r.getWindowProcessController().setStoppedState(stoppedState);
+ } else {
+ r.setWasForceStopped(true);
+ // first launch is computed just before logging, for non-activity types
}
}
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 7356588..b939089 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -259,6 +259,12 @@
private long[] mDisabledCompatChanges;
/**
+ * Set of compat changes for the process that are intended to be logged to logcat.
+ */
+ @GuardedBy("mService")
+ private long[] mLoggableCompatChanges;
+
+ /**
* Who is watching for the death.
*/
@GuardedBy("mService")
@@ -439,6 +445,7 @@
final ProcessRecordNode[] mLinkedNodes = new ProcessRecordNode[NUM_NODE_TYPE];
/** Whether the app was launched from a stopped state and is being unstopped. */
+ @GuardedBy("mService")
volatile boolean mWasForceStopped;
void setStartParams(int startUid, HostingRecord hostingRecord, String seInfo,
@@ -684,6 +691,11 @@
@GuardedBy({"mService", "mProcLock"})
void setPid(int pid) {
+ // If the pid is changing and not the first time pid is being assigned, clear stopped state
+ // So if the process record is re-used for a different pid, it wouldn't keep the state.
+ if (pid != mPid && mPid != 0) {
+ setWasForceStopped(false);
+ }
mPid = pid;
mWindowProcessController.setPid(pid);
mShortStringName = null;
@@ -929,11 +941,21 @@
}
@GuardedBy("mService")
+ long[] getLoggableCompatChanges() {
+ return mLoggableCompatChanges;
+ }
+
+ @GuardedBy("mService")
void setDisabledCompatChanges(long[] disabledCompatChanges) {
mDisabledCompatChanges = disabledCompatChanges;
}
@GuardedBy("mService")
+ void setLoggableCompatChanges(long[] loggableCompatChanges) {
+ mLoggableCompatChanges = loggableCompatChanges;
+ }
+
+ @GuardedBy("mService")
void unlinkDeathRecipient() {
if (mDeathRecipient != null && mThread != null) {
mThread.asBinder().unlinkToDeath(mDeathRecipient, 0);
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index d1bda79..7df5fdd 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -170,6 +170,7 @@
"pixel_connectivity_gps",
"pixel_system_sw_video",
"pixel_watch",
+ "platform_compat",
"platform_security",
"pmw",
"power",
diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING
index feab2c05..bac5132 100644
--- a/services/core/java/com/android/server/am/TEST_MAPPING
+++ b/services/core/java/com/android/server/am/TEST_MAPPING
@@ -8,6 +8,7 @@
{ "include-filter": "android.app.cts.ActivityManagerProcessStateTest" },
{ "include-filter": "android.app.cts.ServiceTest" },
{ "include-filter": "android.app.cts.ActivityManagerFgsBgStartTest" },
+ { "include-filter": "android.app.cts.ForceStopTest" },
{
"include-annotation": "android.platform.test.annotations.Presubmit"
},
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 13a1807..70d447f 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -646,7 +646,7 @@
new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
AppOpsManager.OP_NONE,
getTemporaryAppAllowlistBroadcastOptions(REASON_LOCKED_BOOT_COMPLETED)
- .toBundle(), true,
+ .toBundle(),
false, MY_PID, SYSTEM_UID,
Binder.getCallingUid(), Binder.getCallingPid(), userId);
}
@@ -740,7 +740,7 @@
unlockedIntent.addFlags(
Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
mInjector.broadcastIntent(unlockedIntent, null, null, 0, null,
- null, null, AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+ null, null, AppOpsManager.OP_NONE, null, false, MY_PID, SYSTEM_UID,
Binder.getCallingUid(), Binder.getCallingPid(), userId);
}
@@ -765,7 +765,7 @@
| Intent.FLAG_RECEIVER_FOREGROUND);
mInjector.broadcastIntent(profileUnlockedIntent,
null, null, 0, null, null, null, AppOpsManager.OP_NONE,
- null, false, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
+ null, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
Binder.getCallingPid(), parent.id);
}
}
@@ -824,7 +824,7 @@
initializeUser.run();
}
}, 0, null, null, null, AppOpsManager.OP_NONE,
- null, true, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
+ null, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
Binder.getCallingPid(), userId);
}
}
@@ -876,7 +876,7 @@
new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
AppOpsManager.OP_NONE,
getTemporaryAppAllowlistBroadcastOptions(REASON_BOOT_COMPLETED).toBundle(),
- true, false, MY_PID, SYSTEM_UID, callingUid, callingPid, userId);
+ false, MY_PID, SYSTEM_UID, callingUid, callingPid, userId);
});
}
@@ -1124,7 +1124,7 @@
mInjector.broadcastIntent(stoppingIntent,
null, stoppingReceiver, 0, null, null,
new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
- null, true, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
+ null, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
Binder.getCallingPid(), UserHandle.USER_ALL);
});
}
@@ -1187,7 +1187,7 @@
mInjector.broadcastIntent(shutdownIntent,
null, shutdownReceiver, 0, null, null, null,
AppOpsManager.OP_NONE,
- null, true, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
+ null, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
Binder.getCallingPid(), userId);
}
@@ -1464,7 +1464,7 @@
intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
mInjector.broadcastIntent(intent,
null, null, 0, null, null, null, AppOpsManager.OP_NONE,
- null, false, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
+ null, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
Binder.getCallingPid(), UserHandle.USER_ALL);
// Send PROFILE_INACCESSIBLE broadcast if a profile was stopped
@@ -2404,7 +2404,7 @@
mInjector.broadcastIntent(intent, /* resolvedType= */ null, /* resultTo= */ null,
/* resultCode= */ 0, /* resultData= */ null, /* resultExtras= */ null,
/* requiredPermissions= */ null, AppOpsManager.OP_NONE, /* bOptions= */ null,
- /* ordered= */ false, /* sticky= */ false, MY_PID, SYSTEM_UID,
+ /* sticky= */ false, MY_PID, SYSTEM_UID,
callingUid, callingPid, userId);
}
@@ -2432,7 +2432,7 @@
}
}, /* resultCode= */ 0, /* resultData= */ null, /* resultExtras= */ null,
new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE, /* bOptions= */ null,
- /* ordered= */ true, /* sticky= */ false, MY_PID, SYSTEM_UID,
+ /* sticky= */ false, MY_PID, SYSTEM_UID,
callingUid, callingPid, UserHandle.USER_ALL);
}
@@ -2457,7 +2457,7 @@
intent.putExtra(Intent.EXTRA_USER, UserHandle.of(profileUserId));
mInjector.broadcastIntent(intent,
null, null, 0, null, null, null, AppOpsManager.OP_NONE,
- null, false, false, MY_PID, SYSTEM_UID, callingUid, callingPid,
+ null, false, MY_PID, SYSTEM_UID, callingUid, callingPid,
profileUserId);
}
}
@@ -2476,7 +2476,7 @@
intent.putExtra(Intent.EXTRA_USER, UserHandle.of(profileUserId));
mInjector.broadcastIntent(intent,
null, null, 0, null, null, null, AppOpsManager.OP_NONE,
- null, false, false, MY_PID, SYSTEM_UID, callingUid, callingPid,
+ null, false, MY_PID, SYSTEM_UID, callingUid, callingPid,
profileUserId);
}
intent = new Intent(Intent.ACTION_USER_SWITCHED);
@@ -2489,7 +2489,7 @@
mInjector.broadcastIntent(intent,
null, null, 0, null, null,
new String[] {android.Manifest.permission.MANAGE_USERS},
- AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID, callingUid,
+ AppOpsManager.OP_NONE, null, false, MY_PID, SYSTEM_UID, callingUid,
callingPid, UserHandle.USER_ALL);
}
} finally {
@@ -2513,7 +2513,7 @@
mInjector.broadcastIntent(intent, /* resolvedType= */ null, /* resultTo= */
null, /* resultCode= */ 0, /* resultData= */ null, /* resultExtras= */
null, /* requiredPermissions= */ null, AppOpsManager.OP_NONE, /* bOptions= */
- null, /* ordered= */ false, /* sticky= */ false, MY_PID, SYSTEM_UID,
+ null, /* sticky= */ false, MY_PID, SYSTEM_UID,
Binder.getCallingUid(), Binder.getCallingPid(), parentId);
}
@@ -3576,7 +3576,7 @@
protected int broadcastIntent(Intent intent, String resolvedType,
IIntentReceiver resultTo, int resultCode, String resultData,
Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions,
- boolean ordered, boolean sticky, int callingPid, int callingUid, int realCallingUid,
+ boolean sticky, int callingPid, int callingUid, int realCallingUid,
int realCallingPid, @UserIdInt int userId) {
int logUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
@@ -3585,21 +3585,13 @@
}
EventLog.writeEvent(EventLogTags.UC_SEND_USER_BROADCAST, logUserId, intent.getAction());
- // When the modern broadcast stack is enabled, deliver all our
- // broadcasts as unordered, since the modern stack has better
- // support for sequencing cold-starts, and it supports delivering
- // resultTo for non-ordered broadcasts
- if (mService.mEnableModernQueue) {
- ordered = false;
- }
-
TimingsTraceAndSlog t = new TimingsTraceAndSlog();
// TODO b/64165549 Verify that mLock is not held before calling AMS methods
synchronized (mService) {
t.traceBegin("broadcastIntent-" + userId + "-" + intent.getAction());
final int result = mService.broadcastIntentLocked(null, null, null, intent,
resolvedType, resultTo, resultCode, resultData, resultExtras,
- requiredPermissions, null, null, appOp, bOptions, ordered, sticky,
+ requiredPermissions, null, null, appOp, bOptions, false, sticky,
callingPid, callingUid, realCallingUid, realCallingPid, userId);
t.traceEnd();
return result;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index e0c2425..14428c4 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -157,7 +157,7 @@
* corresponding peers in case of BLE
*/
void addAudioDeviceInInventoryIfNeeded(int deviceType, String address, String peerAddress,
- @AudioDeviceCategory int category) {
+ @AudioDeviceCategory int category, boolean userDefined) {
if (!isBluetoothOutDevice(deviceType)) {
return;
}
@@ -167,7 +167,11 @@
ads = findBtDeviceStateForAddress(peerAddress, deviceType);
}
if (ads != null) {
- if (ads.getAudioDeviceCategory() != category) {
+ // if category is user defined allow to change back to unknown otherwise
+ // do not reset the category back to unknown since it might have been set
+ // before by the user
+ if (ads.getAudioDeviceCategory() != category && (userDefined
+ || category != AUDIO_DEVICE_CATEGORY_UNKNOWN)) {
ads.setAudioDeviceCategory(category);
mDeviceBroker.postUpdatedAdiDeviceState(ads);
mDeviceBroker.postPersistAudioDeviceSettings();
@@ -220,9 +224,9 @@
void addAudioDeviceWithCategoryInInventoryIfNeeded(@NonNull String address,
@AudioDeviceCategory int btAudioDeviceCategory) {
addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_BLE_HEADSET,
- address, "", btAudioDeviceCategory);
+ address, "", btAudioDeviceCategory, /*userDefined=*/true);
addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_BLUETOOTH_A2DP,
- address, "", btAudioDeviceCategory);
+ address, "", btAudioDeviceCategory, /*userDefined=*/true);
}
@AudioDeviceCategory
@@ -1733,7 +1737,7 @@
purgeDevicesRoles_l();
} else {
addAudioDeviceInInventoryIfNeeded(device, address, "",
- BtHelper.getBtDeviceCategory(address));
+ BtHelper.getBtDeviceCategory(address), /*userDefined=*/false);
}
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"SCO " + (AudioSystem.isInputDevice(device) ? "source" : "sink")
@@ -2023,7 +2027,7 @@
updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/);
addAudioDeviceInInventoryIfNeeded(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, "",
- BtHelper.getBtDeviceCategory(address));
+ BtHelper.getBtDeviceCategory(address), /*userDefined=*/false);
}
static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER,
@@ -2357,7 +2361,7 @@
DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
setCurrentAudioRouteNameIfPossible(name, false /*fromA2dp*/);
addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_HEARING_AID, address, "",
- BtHelper.getBtDeviceCategory(address));
+ BtHelper.getBtDeviceCategory(address), /*userDefined=*/false);
new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceAvailable")
.set(MediaMetrics.Property.ADDRESS, address != null ? address : "")
.set(MediaMetrics.Property.DEVICE,
@@ -2488,7 +2492,7 @@
mDeviceBroker.postAccessoryPlugMediaUnmute(device);
setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false);
addAudioDeviceInInventoryIfNeeded(device, address, peerAddress,
- BtHelper.getBtDeviceCategory(address));
+ BtHelper.getBtDeviceCategory(address), /*userDefined=*/false);
}
if (streamType == AudioSystem.STREAM_DEFAULT) {
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 48bf9f4..e915688 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -791,14 +791,12 @@
private void registerAuthenticators() {
BiometricHandlerProvider handlerProvider = mInjector.getBiometricHandlerProvider();
- handlerProvider.getFingerprintHandler().post(() ->
- registerFingerprintSensors(mInjector.getFingerprintAidlInstances(),
- mInjector.getFingerprintConfiguration(getContext()), getContext(),
- mInjector.getFingerprintService()));
- handlerProvider.getFaceHandler().post(() ->
- registerFaceSensors(mInjector.getFaceAidlInstances(),
- mInjector.getFaceConfiguration(getContext()), getContext(),
- mInjector.getFaceService()));
+ registerFingerprintSensors(mInjector.getFingerprintAidlInstances(),
+ mInjector.getFingerprintConfiguration(getContext()), getContext(),
+ mInjector.getFingerprintService(), handlerProvider);
+ registerFaceSensors(mInjector.getFaceAidlInstances(),
+ mInjector.getFaceConfiguration(getContext()), getContext(),
+ mInjector.getFaceService(), handlerProvider);
registerIrisSensors(mInjector.getIrisConfiguration(getContext()));
}
@@ -854,30 +852,38 @@
*/
private static void registerFaceSensors(final String[] faceAidlInstances,
final String[] hidlConfigStrings, final Context context,
- final IFaceService faceService) {
- final FaceSensorConfigurations mFaceSensorConfigurations =
- new FaceSensorConfigurations(hidlConfigStrings != null
- && hidlConfigStrings.length > 0);
-
- if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
- mFaceSensorConfigurations.addHidlConfigs(hidlConfigStrings, context);
+ final IFaceService faceService, final BiometricHandlerProvider handlerProvider) {
+ if ((hidlConfigStrings == null || hidlConfigStrings.length == 0)
+ && (faceAidlInstances == null || faceAidlInstances.length == 0)) {
+ Slog.d(TAG, "No face sensors.");
+ return;
}
- if (faceAidlInstances != null && faceAidlInstances.length > 0) {
- mFaceSensorConfigurations.addAidlConfigs(faceAidlInstances,
- name -> IFace.Stub.asInterface(Binder.allowBlocking(
- ServiceManager.waitForDeclaredService(name))));
- }
+ handlerProvider.getFaceHandler().post(() -> {
+ final FaceSensorConfigurations mFaceSensorConfigurations =
+ new FaceSensorConfigurations(hidlConfigStrings != null
+ && hidlConfigStrings.length > 0);
- if (faceService != null) {
- try {
- faceService.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
- } catch (RemoteException e) {
- Slog.e(TAG, "RemoteException when registering face authenticators", e);
+ if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
+ mFaceSensorConfigurations.addHidlConfigs(hidlConfigStrings, context);
}
- } else if (mFaceSensorConfigurations.hasSensorConfigurations()) {
- Slog.e(TAG, "Face configuration exists, but FaceService is null.");
- }
+
+ if (faceAidlInstances != null && faceAidlInstances.length > 0) {
+ mFaceSensorConfigurations.addAidlConfigs(faceAidlInstances,
+ name -> IFace.Stub.asInterface(Binder.allowBlocking(
+ ServiceManager.waitForDeclaredService(name))));
+ }
+
+ if (faceService != null) {
+ try {
+ faceService.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException when registering face authenticators", e);
+ }
+ } else if (mFaceSensorConfigurations.hasSensorConfigurations()) {
+ Slog.e(TAG, "Face configuration exists, but FaceService is null.");
+ }
+ });
}
/**
@@ -885,30 +891,40 @@
*/
private static void registerFingerprintSensors(final String[] fingerprintAidlInstances,
final String[] hidlConfigStrings, final Context context,
- final IFingerprintService fingerprintService) {
- final FingerprintSensorConfigurations mFingerprintSensorConfigurations =
- new FingerprintSensorConfigurations(!(hidlConfigStrings != null
- && hidlConfigStrings.length > 0));
-
- if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
- mFingerprintSensorConfigurations.addHidlSensors(hidlConfigStrings, context);
+ final IFingerprintService fingerprintService,
+ final BiometricHandlerProvider handlerProvider) {
+ if ((hidlConfigStrings == null || hidlConfigStrings.length == 0)
+ && (fingerprintAidlInstances == null || fingerprintAidlInstances.length == 0)) {
+ Slog.d(TAG, "No fingerprint sensors.");
+ return;
}
- if (fingerprintAidlInstances != null && fingerprintAidlInstances.length > 0) {
- mFingerprintSensorConfigurations.addAidlSensors(fingerprintAidlInstances,
- name -> IFingerprint.Stub.asInterface(Binder.allowBlocking(
- ServiceManager.waitForDeclaredService(name))));
- }
+ handlerProvider.getFingerprintHandler().post(() -> {
+ final FingerprintSensorConfigurations mFingerprintSensorConfigurations =
+ new FingerprintSensorConfigurations(!(hidlConfigStrings != null
+ && hidlConfigStrings.length > 0));
- if (fingerprintService != null) {
- try {
- fingerprintService.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
- } catch (RemoteException e) {
- Slog.e(TAG, "RemoteException when registering fingerprint authenticators", e);
+ if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
+ mFingerprintSensorConfigurations.addHidlSensors(hidlConfigStrings, context);
}
- } else if (mFingerprintSensorConfigurations.hasSensorConfigurations()) {
- Slog.e(TAG, "Fingerprint configuration exists, but FingerprintService is null.");
- }
+
+ if (fingerprintAidlInstances != null && fingerprintAidlInstances.length > 0) {
+ mFingerprintSensorConfigurations.addAidlSensors(fingerprintAidlInstances,
+ name -> IFingerprint.Stub.asInterface(Binder.allowBlocking(
+ ServiceManager.waitForDeclaredService(name))));
+ }
+
+ if (fingerprintService != null) {
+ try {
+ fingerprintService.registerAuthenticatorsLegacy(
+ mFingerprintSensorConfigurations);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException when registering fingerprint authenticators", e);
+ }
+ } else if (mFingerprintSensorConfigurations.hasSensorConfigurations()) {
+ Slog.e(TAG, "Fingerprint configuration exists, but FingerprintService is null.");
+ }
+ });
}
/**
diff --git a/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
index a923daa..e578861 100644
--- a/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
+++ b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
@@ -16,6 +16,9 @@
package com.android.server.biometrics;
+import static android.os.Process.THREAD_PRIORITY_DEFAULT;
+import static android.os.Process.THREAD_PRIORITY_DISPLAY;
+
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
@@ -27,9 +30,9 @@
private static final BiometricHandlerProvider sBiometricHandlerProvider =
new BiometricHandlerProvider();
- private final Handler mBiometricsCallbackHandler;
- private final Handler mFingerprintHandler;
- private final Handler mFaceHandler;
+ private Handler mBiometricsCallbackHandler;
+ private Handler mFingerprintHandler;
+ private Handler mFaceHandler;
/**
* @return an instance of {@link BiometricHandlerProvider} which contains the three
@@ -39,16 +42,16 @@
return sBiometricHandlerProvider;
}
- private BiometricHandlerProvider() {
- mBiometricsCallbackHandler = getNewHandler("BiometricsCallbackHandler");
- mFingerprintHandler = getNewHandler("FingerprintHandler");
- mFaceHandler = getNewHandler("FaceHandler");
- }
+ private BiometricHandlerProvider() {}
/**
* @return the handler to process all biometric callback operations
*/
public synchronized Handler getBiometricCallbackHandler() {
+ if (mBiometricsCallbackHandler == null) {
+ mBiometricsCallbackHandler = getNewHandler("BiometricsCallbackHandler",
+ THREAD_PRIORITY_DISPLAY);
+ }
return mBiometricsCallbackHandler;
}
@@ -56,6 +59,9 @@
* @return the handler to process all face related biometric operations
*/
public synchronized Handler getFaceHandler() {
+ if (mFaceHandler == null) {
+ mFaceHandler = getNewHandler("FaceHandler", THREAD_PRIORITY_DEFAULT);
+ }
return mFaceHandler;
}
@@ -63,12 +69,15 @@
* @return the handler to process all fingerprint related biometric operations
*/
public synchronized Handler getFingerprintHandler() {
+ if (mFingerprintHandler == null) {
+ mFingerprintHandler = getNewHandler("FingerprintHandler", THREAD_PRIORITY_DEFAULT);
+ }
return mFingerprintHandler;
}
- private Handler getNewHandler(String tag) {
+ private Handler getNewHandler(String tag, int priority) {
if (Flags.deHidl()) {
- HandlerThread handlerThread = new HandlerThread(tag);
+ HandlerThread handlerThread = new HandlerThread(tag, priority);
handlerThread.start();
return new Handler(handlerThread.getLooper());
}
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
index 03acf72..d93ff9d 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
@@ -82,13 +82,6 @@
Slogf.w(TAG, "No module %s with id %d (HAL AIDL)", name, moduleId);
return;
}
- try {
- radioModule.setInternalHalCallback();
- } catch (RemoteException ex) {
- Slogf.wtf(TAG, ex, "Broadcast radio module %s with id %d (HAL AIDL) "
- + "cannot register HAL callback", name, moduleId);
- return;
- }
if (DEBUG) {
Slogf.d(TAG, "Loaded broadcast radio module %s with id %d (HAL AIDL)",
name, moduleId);
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
index 4b3444d..cd86510 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
@@ -246,10 +246,6 @@
return mProperties;
}
- void setInternalHalCallback() throws RemoteException {
- mService.setTunerCallback(mHalTunerCallback);
- }
-
TunerSession openSession(android.hardware.radio.ITunerCallback userCb)
throws RemoteException {
mLogger.logRadioEvent("Open TunerSession");
@@ -257,10 +253,14 @@
Boolean antennaConnected;
RadioManager.ProgramInfo currentProgramInfo;
synchronized (mLock) {
+ boolean isFirstTunerSession = mAidlTunerSessions.isEmpty();
tunerSession = new TunerSession(this, mService, userCb);
mAidlTunerSessions.add(tunerSession);
antennaConnected = mAntennaConnected;
currentProgramInfo = mCurrentProgramInfo;
+ if (isFirstTunerSession) {
+ mService.setTunerCallback(mHalTunerCallback);
+ }
}
// Propagate state to new client.
// Note: These callbacks are invoked while holding mLock to prevent race conditions
@@ -284,7 +284,6 @@
synchronized (mLock) {
tunerSessions = new TunerSession[mAidlTunerSessions.size()];
mAidlTunerSessions.toArray(tunerSessions);
- mAidlTunerSessions.clear();
}
for (TunerSession tunerSession : tunerSessions) {
@@ -402,6 +401,14 @@
mAidlTunerSessions.remove(tunerSession);
}
onTunerSessionProgramListFilterChanged(null);
+ if (mAidlTunerSessions.isEmpty()) {
+ try {
+ mService.unsetTunerCallback();
+ } catch (RemoteException ex) {
+ Slogf.wtf(TAG, ex, "Failed to unregister HAL callback for module %d",
+ mProperties.getId());
+ }
+ }
}
// add to mHandler queue
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index 9102cfd..79025d0 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -25,6 +25,7 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.os.Build;
import android.os.Environment;
import android.text.TextUtils;
import android.util.LongArray;
@@ -72,7 +73,6 @@
* been configured.
*/
final class CompatConfig {
-
private static final String TAG = "CompatConfig";
private static final String APP_COMPAT_DATA_DIR = "/data/misc/appcompat";
private static final String STATIC_OVERRIDES_PRODUCT_DIR = "/product/etc/appcompat";
@@ -149,6 +149,56 @@
}
/**
+ * Retrieves the set of changes that are intended to be logged. This includes changes that
+ * target the most recent SDK version and are not disabled.
+ *
+ * @param app the app in question
+ * @return a sorted long array of change IDs
+ */
+ long[] getLoggableChanges(ApplicationInfo app) {
+ LongArray loggable = new LongArray(mChanges.size());
+ for (CompatChange c : mChanges.values()) {
+ long changeId = c.getId();
+ boolean isLatestSdk = isChangeTargetingLatestSdk(c, app.targetSdkVersion);
+ if (c.isEnabled(app, mAndroidBuildClassifier) && isLatestSdk) {
+ loggable.add(changeId);
+ }
+ }
+ final long[] sortedChanges = loggable.toArray();
+ Arrays.sort(sortedChanges);
+ return sortedChanges;
+ }
+
+ /**
+ * Whether the change indicated by the given changeId is targeting the latest SDK version.
+ * @param c the change for which to check the target SDK version
+ * @param appSdkVersion the target sdk version of the app
+ * @return true if the changeId targets the current sdk version or the current development
+ * version.
+ */
+ boolean isChangeTargetingLatestSdk(CompatChange c, int appSdkVersion) {
+ int maxTargetSdk = maxTargetSdkForCompatChange(c) + 1;
+ if (maxTargetSdk <= 0) {
+ // No max target sdk found.
+ return false;
+ }
+
+ return maxTargetSdk == Build.VERSION_CODES.CUR_DEVELOPMENT || maxTargetSdk == appSdkVersion;
+ }
+
+ /**
+ * Retrieves the CompatChange associated with the given changeId. Will return null if the
+ * changeId is not found. Used only for performance improvement purposes, in order to reduce
+ * lookups.
+ *
+ * @param changeId for which to look up the CompatChange
+ * @return the found compat change, or null if not found.
+ */
+ CompatChange getCompatChange(long changeId) {
+ return mChanges.get(changeId);
+ }
+
+ /**
* Looks up a change ID by name.
*
* @param name name of the change to look up
@@ -164,7 +214,7 @@
}
/**
- * Checks if a given change is enabled for a given application.
+ * Checks if a given change id is enabled for a given application.
*
* @param changeId the ID of the change in question
* @param app app to check for
@@ -173,6 +223,18 @@
*/
boolean isChangeEnabled(long changeId, ApplicationInfo app) {
CompatChange c = mChanges.get(changeId);
+ return isChangeEnabled(c, app);
+ }
+
+ /**
+ * Checks if a given change is enabled for a given application.
+ *
+ * @param c the CompatChange in question
+ * @param app the app to check for
+ * @return {@code true} if the change is enabled for this app. Also returns {@code true} if the
+ * change ID is not known, as unknown changes are enabled by default.
+ */
+ boolean isChangeEnabled(CompatChange c, ApplicationInfo app) {
if (c == null) {
// we know nothing about this change: default behaviour is enabled.
return true;
@@ -301,9 +363,21 @@
/**
* Returns the maximum SDK version for which this change can be opted in (or -1 if it is not
* target SDK gated).
+ *
+ * @param changeId the id of the CompatChange to check for the max target sdk
*/
int maxTargetSdkForChangeIdOptIn(long changeId) {
CompatChange c = mChanges.get(changeId);
+ return maxTargetSdkForCompatChange(c);
+ }
+
+ /**
+ * Returns the maximum SDK version for which this change can be opted in (or -1 if it is not
+ * target SDK gated).
+ *
+ * @param c the CompatChange to check for the max target sdk
+ */
+ int maxTargetSdkForCompatChange(CompatChange c) {
if (c != null && c.getEnableSinceTargetSdk() != -1) {
return c.getEnableSinceTargetSdk() - 1;
}
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 6cca130..f8fd0a0 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -120,8 +120,16 @@
reportChangeInternal(changeId, uid, ChangeReporter.STATE_LOGGED);
}
+ /**
+ * Report the change, but skip over the sdk target version check. This can be used to force the
+ * debug logs.
+ *
+ * @param changeId of the change to report
+ * @param uid of the user
+ * @param state of the change - enabled/disabled/logged
+ */
private void reportChangeInternal(long changeId, int uid, int state) {
- mChangeReporter.reportChange(uid, changeId, state);
+ mChangeReporter.reportChange(uid, changeId, state, true);
}
@Override
@@ -164,15 +172,25 @@
}
/**
- * Internal version of {@link #isChangeEnabled(long, ApplicationInfo)}.
+ * Internal version of {@link #isChangeEnabled(long, ApplicationInfo)}. If the provided appInfo
+ * is not null, also reports the change.
+ *
+ * @param changeId of the change to report
+ * @param appInfo the app to check
*
* <p>Does not perform costly permission check.
*/
public boolean isChangeEnabledInternal(long changeId, ApplicationInfo appInfo) {
- boolean enabled = isChangeEnabledInternalNoLogging(changeId, appInfo);
+ // Fetch the CompatChange. This is done here instead of in mCompatConfig to avoid multiple
+ // fetches.
+ CompatChange c = mCompatConfig.getCompatChange(changeId);
+
+ boolean enabled = mCompatConfig.isChangeEnabled(c, appInfo);
+ int state = enabled ? ChangeReporter.STATE_ENABLED : ChangeReporter.STATE_DISABLED;
if (appInfo != null) {
- reportChangeInternal(changeId, appInfo.uid,
- enabled ? ChangeReporter.STATE_ENABLED : ChangeReporter.STATE_DISABLED);
+ boolean isTargetingLatestSdk =
+ mCompatConfig.isChangeTargetingLatestSdk(c, appInfo.targetSdkVersion);
+ mChangeReporter.reportChange(appInfo.uid, changeId, state, isTargetingLatestSdk);
}
return enabled;
}
@@ -399,6 +417,19 @@
}
/**
+ * Retrieves the set of changes that should be logged for a given app. Any change ID not in the
+ * returned array is ignored for logging purposes.
+ *
+ * @param appInfo The app in question
+ * @return A sorted long array of change IDs. We use a primitive array to minimize memory
+ * footprint: Every app process will store this array statically so we aim to reduce
+ * overhead as much as possible.
+ */
+ public long[] getLoggableChanges(ApplicationInfo appInfo) {
+ return mCompatConfig.getLoggableChanges(appInfo);
+ }
+
+ /**
* Look up a change ID by name.
*
* @param name Name of the change to look up
diff --git a/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java b/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java
new file mode 100644
index 0000000..133c79f
--- /dev/null
+++ b/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.crashrecovery;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
+import android.net.ConnectivityModuleConnector;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.server.PackageWatchdog;
+import com.android.server.pm.ApexManager;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Provides helper methods for the CrashRecovery APEX
+ *
+ * @hide
+ */
+public final class CrashRecoveryHelper {
+ private static final String TAG = "CrashRecoveryHelper";
+
+ private final ApexManager mApexManager;
+ private final Context mContext;
+ private final ConnectivityModuleConnector mConnectivityModuleConnector;
+
+
+ /** @hide */
+ public CrashRecoveryHelper(@NonNull Context context) {
+ mContext = context;
+ mApexManager = ApexManager.getInstance();
+ mConnectivityModuleConnector = ConnectivityModuleConnector.getInstance();
+ }
+
+ /**
+ * Returns true if the package name is the name of a module.
+ * If the package is an APK inside an APEX then it will use the parent's APEX package name
+ * do determine if it is a module or not.
+ * @hide
+ */
+ @AnyThread
+ public boolean isModule(@NonNull String packageName) {
+ String apexPackageName =
+ mApexManager.getActiveApexPackageNameContainingPackage(packageName);
+ if (apexPackageName != null) {
+ packageName = apexPackageName;
+ }
+
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ return pm.getModuleInfo(packageName, 0) != null;
+ } catch (PackageManager.NameNotFoundException ignore) {
+ return false;
+ }
+ }
+
+ /**
+ * Register health listeners for explicit package failures.
+ * Currently only registering for Connectivity Module health.
+ * @hide
+ */
+ public void registerConnectivityModuleHealthListener(@NonNull int failureReason) {
+ // register listener for ConnectivityModule
+ mConnectivityModuleConnector.registerHealthListener(
+ packageName -> {
+ final VersionedPackage pkg = getVersionedPackage(packageName);
+ if (pkg == null) {
+ Slog.wtf(TAG, "NetworkStack failed but could not find its package");
+ return;
+ }
+ final List<VersionedPackage> pkgList = Collections.singletonList(pkg);
+ PackageWatchdog.getInstance(mContext).onPackageFailure(pkgList, failureReason);
+ });
+ }
+
+ @Nullable
+ private VersionedPackage getVersionedPackage(String packageName) {
+ final PackageManager pm = mContext.getPackageManager();
+ if (pm == null || TextUtils.isEmpty(packageName)) {
+ return null;
+ }
+ try {
+ final long versionCode = getPackageInfo(packageName).getLongVersionCode();
+ return new VersionedPackage(packageName, versionCode);
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Gets PackageInfo for the given package. Matches any user and apex.
+ *
+ * @throws PackageManager.NameNotFoundException if no such package is installed.
+ */
+ private PackageInfo getPackageInfo(String packageName)
+ throws PackageManager.NameNotFoundException {
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ // The MATCH_ANY_USER flag doesn't mix well with the MATCH_APEX
+ // flag, so make two separate attempts to get the package info.
+ // We don't need both flags at the same time because we assume
+ // apex files are always installed for all users.
+ return pm.getPackageInfo(packageName, PackageManager.MATCH_ANY_USER);
+ } catch (PackageManager.NameNotFoundException e) {
+ return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/crashrecovery/OWNERS b/services/core/java/com/android/server/crashrecovery/OWNERS
new file mode 100644
index 0000000..daa0211
--- /dev/null
+++ b/services/core/java/com/android/server/crashrecovery/OWNERS
@@ -0,0 +1,3 @@
+ancr@google.com
+harshitmahajan@google.com
+robertogil@google.com
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 38051c1..177c345 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -19,8 +19,9 @@
import static android.Manifest.permission.CONTROL_DEVICE_STATE;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.hardware.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
-import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE_IDENTIFIER;
import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE_IDENTIFIER;
@@ -75,6 +76,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
@@ -109,6 +111,12 @@
private static final String TAG = "DeviceStateManagerService";
private static final boolean DEBUG = false;
+ /** {@link DeviceState} to model an invalid device state */
+ // TODO(b/328314031): Investigate how we can remove this constant
+ private static final DeviceState INVALID_DEVICE_STATE = new DeviceState(
+ new DeviceState.Configuration.Builder(INVALID_DEVICE_STATE_IDENTIFIER,
+ "INVALID").build());
+
private final Object mLock = new Object();
// Handler on the {@link DisplayThread} used to dispatch calls to the policy and to registered
// callbacks though its handler (mHandler). Provides a guarantee of callback order when
@@ -354,16 +362,22 @@
}
/** Returns the list of currently supported device states. */
- DeviceState[] getSupportedStates() {
+ List<DeviceState> getSupportedStates() {
synchronized (mLock) {
- DeviceState[] supportedStates = new DeviceState[mDeviceStates.size()];
- for (int i = 0; i < supportedStates.length; i++) {
- supportedStates[i] = mDeviceStates.valueAt(i);
- }
- return supportedStates;
+ return getSupportedStatesLocked();
}
}
+ /** Returns the list of currently supported device states */
+ @GuardedBy("mLock")
+ private List<DeviceState> getSupportedStatesLocked() {
+ List<DeviceState> supportedStates = new ArrayList<>(mDeviceStates.size());
+ for (int i = 0; i < mDeviceStates.size(); i++) {
+ supportedStates.add(i, mDeviceStates.valueAt(i));
+ }
+ return supportedStates;
+ }
+
/** Returns the list of currently supported device state identifiers. */
private int[] getSupportedStateIdentifiersLocked() {
int[] supportedStates = new int[mDeviceStates.size()];
@@ -375,20 +389,46 @@
/**
* Returns the current {@link DeviceStateInfo} of the device. If there has been no base state
- * or committed state provided, {@link DeviceStateManager#INVALID_DEVICE_STATE} will be returned
+ * or committed state provided, {@link #INVALID_DEVICE_STATE} will be returned
* respectively. The supported states will always be included.
*
*/
@GuardedBy("mLock")
@NonNull
private DeviceStateInfo getDeviceStateInfoLocked() {
- final int[] supportedStates = getSupportedStateIdentifiersLocked();
- final int baseState =
- mBaseState.isPresent() ? mBaseState.get().getIdentifier() : INVALID_DEVICE_STATE;
- final int currentState = mCommittedState.isPresent() ? mCommittedState.get().getIdentifier()
- : INVALID_DEVICE_STATE;
+ final List<DeviceState> supportedStates = getSupportedStatesLocked();
+ final DeviceState baseState = mBaseState.orElse(null);
+ final DeviceState currentState = mCommittedState.orElse(null);
- return new DeviceStateInfo(supportedStates, baseState, currentState);
+ return new DeviceStateInfo(supportedStates,
+ baseState != null ? baseState : INVALID_DEVICE_STATE,
+ createMergedDeviceState(currentState, baseState));
+ }
+
+ /**
+ * Returns a {@link DeviceState} with the combined properties of the current system state, as
+ * well as the physical property that corresponds to the base state (physical hardware state) of
+ * the device.
+ */
+ private DeviceState createMergedDeviceState(@Nullable DeviceState committedState,
+ @Nullable DeviceState baseState) {
+ if (committedState == null) {
+ return INVALID_DEVICE_STATE;
+ }
+
+ Set<@DeviceState.DeviceStateProperties Integer> systemProperties =
+ committedState.getConfiguration().getSystemProperties();
+
+ Set<@DeviceState.DeviceStateProperties Integer> physicalProperties =
+ baseState != null ? baseState.getConfiguration().getPhysicalProperties()
+ : Collections.emptySet();
+
+ DeviceState.Configuration deviceStateConfiguration = new DeviceState.Configuration.Builder(
+ committedState.getIdentifier(), committedState.getName())
+ .setSystemProperties(systemProperties)
+ .setPhysicalProperties(physicalProperties)
+ .build();
+ return new DeviceState(deviceStateConfiguration);
}
@VisibleForTesting
@@ -408,7 +448,7 @@
mDeviceStates.clear();
for (int i = 0; i < supportedDeviceStates.length; i++) {
DeviceState state = supportedDeviceStates[i];
- if (state.hasFlag(FLAG_CANCEL_OVERRIDE_REQUESTS)) {
+ if (state.hasProperty(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS)) {
hasTerminalDeviceState = true;
}
mDeviceStates.put(state.getIdentifier(), state);
@@ -436,7 +476,7 @@
private void setRearDisplayStateLocked() {
int rearDisplayIdentifier = getContext().getResources().getInteger(
R.integer.config_deviceStateRearDisplay);
- if (rearDisplayIdentifier != INVALID_DEVICE_STATE) {
+ if (rearDisplayIdentifier != INVALID_DEVICE_STATE_IDENTIFIER) {
mRearDisplayState = mDeviceStates.get(rearDisplayIdentifier);
}
}
@@ -453,7 +493,7 @@
* Returns the {@link DeviceState} with the supplied {@code identifier}, or {@code null} if
* there is no device state with the identifier.
*/
- @Nullable
+ @NonNull
private Optional<DeviceState> getStateLocked(int identifier) {
return Optional.ofNullable(mDeviceStates.get(identifier));
}
@@ -468,7 +508,7 @@
private void setBaseState(int identifier) {
synchronized (mLock) {
final Optional<DeviceState> baseStateOptional = getStateLocked(identifier);
- if (!baseStateOptional.isPresent()) {
+ if (baseStateOptional.isEmpty()) {
throw new IllegalArgumentException("Base state is not supported");
}
@@ -484,7 +524,7 @@
}
mBaseState = Optional.of(baseState);
- if (baseState.hasFlag(FLAG_CANCEL_OVERRIDE_REQUESTS)) {
+ if (baseState.hasProperty(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS)) {
mOverrideRequestController.cancelOverrideRequest();
}
mOverrideRequestController.handleBaseStateChanged(identifier);
@@ -1023,7 +1063,7 @@
}
private Set<Integer> readFoldedStates() {
- Set<Integer> foldedStates = new HashSet();
+ Set<Integer> foldedStates = new HashSet<>();
int[] mFoldedStatesArray = getContext().getResources().getIntArray(
com.android.internal.R.array.config_foldedDeviceStates);
for (int i = 0; i < mFoldedStatesArray.length; i++) {
@@ -1338,7 +1378,7 @@
}
int identifier = mActiveOverride.get().getRequestedStateIdentifier();
DeviceState deviceState = mDeviceStates.get(identifier);
- return deviceState.hasFlag(DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
+ return deviceState.hasProperty(PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
}
private class OverrideRequestScreenObserver implements
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
index 02c9bb3..97913de3 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
@@ -25,7 +25,7 @@
import android.os.ShellCommand;
import java.io.PrintWriter;
-import java.util.Arrays;
+import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -177,17 +177,18 @@
}
private int runPrintStates(PrintWriter pw) {
- DeviceState[] states = mService.getSupportedStates();
+ List<DeviceState> states = mService.getSupportedStates();
pw.print("Supported states: [\n");
- for (int i = 0; i < states.length; i++) {
- pw.print(" " + states[i] + ",\n");
+ for (int i = 0; i < states.size(); i++) {
+ pw.print(" " + states.get(i) + ",\n");
}
pw.println("]");
return 0;
}
private int runPrintStatesSimple(PrintWriter pw) {
- pw.print(Arrays.stream(mService.getSupportedStates())
+ pw.print(mService.getSupportedStates()
+ .stream()
.map(DeviceState::getIdentifier)
.map(Object::toString)
.collect(Collectors.joining(",")));
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java b/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java
index f9aefd0..46478c1 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateNotificationController.java
@@ -323,7 +323,7 @@
for (int i = 0; i < stateIdentifiers.length; i++) {
int identifier = stateIdentifiers[i];
- if (identifier == DeviceStateManager.INVALID_DEVICE_STATE) {
+ if (identifier == DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER) {
continue;
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
index b865c1d9..8d07609 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
@@ -28,8 +28,8 @@
import java.lang.annotation.RetentionPolicy;
/**
- * Responsible for providing the set of supported {@link DeviceState device states} as well as the
- * current device state.
+ * Responsible for providing the set of supported {@link DeviceState.Configuration device states} as
+ * well as the current device state.
*
* @see DeviceStatePolicy
*/
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequest.java b/services/core/java/com/android/server/devicestate/OverrideRequest.java
index d92629f..df7301e 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequest.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequest.java
@@ -72,8 +72,9 @@
@Retention(RetentionPolicy.SOURCE)
public @interface OverrideRequestType {}
- OverrideRequest(IBinder token, int pid, int uid, @NonNull DeviceState requestedState,
- @DeviceStateRequest.RequestFlags int flags, @OverrideRequestType int requestType) {
+ OverrideRequest(IBinder token, int pid, int uid,
+ @NonNull DeviceState requestedState, @DeviceStateRequest.RequestFlags int flags,
+ @OverrideRequestType int requestType) {
mToken = token;
mPid = pid;
mUid = uid;
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
index 6c3fd83d..041ab3a 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
@@ -205,8 +205,8 @@
}
if (mRequest != null && mRequest.getPid() == pid) {
- if (mRequest.getRequestedDeviceState().hasFlag(
- DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP)) {
+ if (mRequest.getRequestedDeviceState().hasProperty(
+ DeviceState.PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP)) {
cancelCurrentRequestLocked();
return;
}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index b2a738f..3d95fee 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -403,15 +403,14 @@
brightnessEvent.setRecommendedBrightness(mScreenAutoBrightness);
brightnessEvent.setFlags(brightnessEvent.getFlags()
| (!mAmbientLuxValid ? BrightnessEvent.FLAG_INVALID_LUX : 0)
- | (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE
- ? BrightnessEvent.FLAG_DOZE_SCALE : 0));
+ | (shouldApplyDozeScaleFactor() ? BrightnessEvent.FLAG_DOZE_SCALE : 0));
brightnessEvent.setAutoBrightnessMode(getMode());
}
if (!mAmbientLuxValid) {
return PowerManager.BRIGHTNESS_INVALID_FLOAT;
}
- if (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE) {
+ if (shouldApplyDozeScaleFactor()) {
return mScreenAutoBrightness * mDozeScaleFactor;
}
return mScreenAutoBrightness;
@@ -434,7 +433,7 @@
float brightness = mCurrentBrightnessMapper.getBrightness(mLastObservedLux,
mForegroundAppPackageName, mForegroundAppCategory);
- if (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE) {
+ if (shouldApplyDozeScaleFactor()) {
brightness *= mDozeScaleFactor;
}
@@ -443,8 +442,7 @@
brightnessEvent.setRecommendedBrightness(brightness);
brightnessEvent.setFlags(brightnessEvent.getFlags()
| (mLastObservedLux == INVALID_LUX ? BrightnessEvent.FLAG_INVALID_LUX : 0)
- | (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE
- ? BrightnessEvent.FLAG_DOZE_SCALE : 0));
+ | (shouldApplyDozeScaleFactor() ? BrightnessEvent.FLAG_DOZE_SCALE : 0));
brightnessEvent.setAutoBrightnessMode(getMode());
}
return brightness;
@@ -1263,6 +1261,12 @@
}
}
+ private boolean shouldApplyDozeScaleFactor() {
+ // Don't apply the doze scale factor if we have a designated brightness curve for doze
+ return mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE
+ && getMode() != AUTO_BRIGHTNESS_MODE_DOZE;
+ }
+
private class ShortTermModel {
// When the short term model is invalidated, we don't necessarily reset it (i.e. clear the
// user's adjustment) immediately, but wait for a drastic enough change in the ambient
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
index 40b2f5a..10030b3 100644
--- a/services/core/java/com/android/server/display/BrightnessRangeController.java
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -21,6 +21,7 @@
import android.os.IBinder;
import android.os.PowerManager;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.brightness.clamper.HdrClamper;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -30,8 +31,7 @@
class BrightnessRangeController {
private final HighBrightnessModeController mHbmController;
- private final NormalBrightnessModeController mNormalBrightnessModeController =
- new NormalBrightnessModeController();
+ private final NormalBrightnessModeController mNormalBrightnessModeController;
private final HdrClamper mHdrClamper;
@@ -45,17 +45,21 @@
Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig, Handler handler,
DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) {
this(hbmController, modeChangeCallback, displayDeviceConfig,
+ new NormalBrightnessModeController(),
new HdrClamper(modeChangeCallback::run, new Handler(handler.getLooper())), flags,
displayToken, info);
}
+ @VisibleForTesting
BrightnessRangeController(HighBrightnessModeController hbmController,
Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig,
+ NormalBrightnessModeController normalBrightnessModeController,
HdrClamper hdrClamper, DisplayManagerFlags flags, IBinder displayToken,
DisplayDeviceInfo info) {
mHbmController = hbmController;
mModeChangeCallback = modeChangeCallback;
mHdrClamper = hdrClamper;
+ mNormalBrightnessModeController = normalBrightnessModeController;
mUseHdrClamper = flags.isHdrClamperEnabled();
mUseNbmController = flags.isNbmControllerEnabled();
if (mUseNbmController) {
@@ -126,8 +130,11 @@
float getCurrentBrightnessMax() {
- if (mUseNbmController && mHbmController.getHighBrightnessMode()
- == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF) {
+ // nbmController might adjust maxBrightness only if device does not support HBM or
+ // hbm is currently not allowed
+ if (mUseNbmController
+ && (!mHbmController.deviceSupportsHbm()
+ || !mHbmController.isHbmCurrentlyAllowed())) {
return Math.min(mHbmController.getCurrentBrightnessMax(),
mNormalBrightnessModeController.getCurrentBrightnessMax());
}
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index e54f30f..146810f 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -51,7 +51,7 @@
class DeviceStateToLayoutMap {
private static final String TAG = "DeviceStateToLayoutMap";
- public static final int STATE_DEFAULT = DeviceStateManager.INVALID_DEVICE_STATE;
+ public static final int STATE_DEFAULT = DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
// Direction of the display relative to the default display, whilst in this state
private static final int POSITION_UNKNOWN = Layout.Display.POSITION_UNKNOWN;
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 3b05b47..a7748f4 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -44,6 +44,15 @@
* </p>
*/
abstract class DisplayDevice {
+ /**
+ * Maximum acceptable anisotropy for the output image.
+ *
+ * Necessary to avoid unnecessary scaling when pixels are almost square, as they are non ideal
+ * anyway. For external displays, we expect an anisotropy of about 2% even if the pixels
+ * are, in fact, square due to the imprecision of the display's actual size (parsed from edid
+ * and rounded to the nearest cm).
+ */
+ static final float MAX_ANISOTROPY = 1.025f;
private static final String TAG = "DisplayDevice";
private static final Display.Mode EMPTY_DISPLAY_MODE = new Display.Mode.Builder().build();
@@ -69,13 +78,21 @@
// Do not use for any other purpose.
DisplayDeviceInfo mDebugLastLoggedDeviceInfo;
- public DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId,
+ private final boolean mIsAnisotropyCorrectionEnabled;
+
+ DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId,
Context context) {
+ this(displayAdapter, displayToken, uniqueId, context, false);
+ }
+
+ DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId,
+ Context context, boolean isAnisotropyCorrectionEnabled) {
mDisplayAdapter = displayAdapter;
mDisplayToken = displayToken;
mUniqueId = uniqueId;
mDisplayDeviceConfig = null;
mContext = context;
+ mIsAnisotropyCorrectionEnabled = isAnisotropyCorrectionEnabled;
}
/**
@@ -143,8 +160,17 @@
DisplayDeviceInfo displayDeviceInfo = getDisplayDeviceInfoLocked();
final boolean isRotated = mCurrentOrientation == ROTATION_90
|| mCurrentOrientation == ROTATION_270;
- return isRotated ? new Point(displayDeviceInfo.height, displayDeviceInfo.width)
- : new Point(displayDeviceInfo.width, displayDeviceInfo.height);
+ var width = displayDeviceInfo.width;
+ var height = displayDeviceInfo.height;
+ if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.yDpi > 0
+ && displayDeviceInfo.xDpi > 0) {
+ if (displayDeviceInfo.xDpi > displayDeviceInfo.yDpi * MAX_ANISOTROPY) {
+ height = (int) (height * displayDeviceInfo.xDpi / displayDeviceInfo.yDpi + 0.5);
+ } else if (displayDeviceInfo.xDpi * MAX_ANISOTROPY < displayDeviceInfo.yDpi) {
+ width = (int) (width * displayDeviceInfo.yDpi / displayDeviceInfo.xDpi + 0.5);
+ }
+ }
+ return isRotated ? new Point(height, width) : new Point(width, height);
}
/**
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index ad89444..ce7c224 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -284,6 +284,8 @@
// Display mode chosen by user.
private Display.Mode mUserPreferredMode;
+ @HdrConversionMode.ConversionMode
+ private final int mDefaultHdrConversionMode;
// HDR conversion mode chosen by user
@GuardedBy("mSyncRoot")
private HdrConversionMode mHdrConversionMode = null;
@@ -582,6 +584,10 @@
mDefaultDisplayDefaultColorMode = mContext.getResources().getInteger(
com.android.internal.R.integer.config_defaultDisplayDefaultColorMode);
mDefaultDisplayTopInset = SystemProperties.getInt(PROP_DEFAULT_DISPLAY_TOP_INSET, -1);
+ mDefaultHdrConversionMode = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_enableDefaultHdrConversionPassthrough)
+ ? HdrConversionMode.HDR_CONVERSION_PASSTHROUGH
+ : HdrConversionMode.HDR_CONVERSION_SYSTEM;
float[] lux = getFloatArray(resources.obtainTypedArray(
com.android.internal.R.array.config_minimumBrightnessCurveLux));
float[] nits = getFloatArray(resources.obtainTypedArray(
@@ -2236,7 +2242,7 @@
@GuardedBy("mSyncRoot")
void updateHdrConversionModeSettingsLocked() {
final int conversionMode = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.HDR_CONVERSION_MODE, HdrConversionMode.HDR_CONVERSION_SYSTEM);
+ Settings.Global.HDR_CONVERSION_MODE, mDefaultHdrConversionMode);
final int preferredHdrOutputType = conversionMode == HdrConversionMode.HDR_CONVERSION_FORCE
? Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.HDR_FORCE_CONVERSION_TYPE,
@@ -2461,7 +2467,7 @@
return mHdrConversionMode;
}
}
- return new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM);
+ return new HdrConversionMode(mDefaultHdrConversionMode);
}
HdrConversionMode getHdrConversionModeInternal() {
@@ -2473,6 +2479,14 @@
mode = mOverrideHdrConversionMode != null
? mOverrideHdrConversionMode
: mHdrConversionMode;
+ // Handle default: PASSTHROUGH. Don't include the system-preferred type.
+ if (mode == null
+ && mDefaultHdrConversionMode == HdrConversionMode.HDR_CONVERSION_PASSTHROUGH) {
+ return new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_PASSTHROUGH);
+ }
+ // Handle default or current mode: SYSTEM. Include the system preferred type.
+ // mOverrideHdrConversionMode and mHdrConversionMode do not include the system
+ // preferred type, it is kept separately in mSystemPreferredHdrOutputType.
if (mode == null
|| mode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM) {
return new HdrConversionMode(
@@ -5021,7 +5035,7 @@
*/
class DeviceStateListener implements DeviceStateManager.DeviceStateCallback {
// Base state corresponds to the physical state of the device
- private int mBaseState = DeviceStateManager.INVALID_DEVICE_STATE;
+ private int mBaseState = DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
@Override
public void onStateChanged(int deviceState) {
diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
index 91e560e..b1defe9 100644
--- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
+++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
@@ -113,4 +113,14 @@
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
}
+
+ @Override
+ public float getBrightness() {
+ return mDisplayPowerController.getScreenBrightnessSetting();
+ }
+
+ @Override
+ public float getDozeBrightness() {
+ return mDisplayPowerController.getDozeBrightnessForOffload();
+ }
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 7f014f6..77a43d0 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -487,6 +487,9 @@
private DisplayOffloadSession mDisplayOffloadSession;
+ // Used to scale the brightness in doze mode
+ private float mDozeScaleFactor;
+
/**
* Creates the display power controller.
*/
@@ -547,6 +550,9 @@
loadBrightnessRampRates();
mSkipScreenOnBrightnessRamp = resources.getBoolean(
R.bool.config_skipScreenOnBrightnessRamp);
+ mDozeScaleFactor = context.getResources().getFraction(
+ R.fraction.config_screenAutoBrightnessDozeScaleFactor,
+ 1, 1);
Runnable modeChangeCallback = () -> {
sendUpdatePowerState();
@@ -1042,10 +1048,6 @@
}
if (defaultModeBrightnessMapper != null) {
- final float dozeScaleFactor = context.getResources().getFraction(
- R.fraction.config_screenAutoBrightnessDozeScaleFactor,
- 1, 1);
-
// Ambient Lux - Active Mode Brightness Thresholds
float[] ambientBrighteningThresholds =
mDisplayDeviceConfig.getAmbientBrighteningPercentages();
@@ -1156,7 +1158,7 @@
mAutomaticBrightnessController = mInjector.getAutomaticBrightnessController(
this, handler.getLooper(), mSensorManager, mLightSensor,
brightnessMappers, lightSensorWarmUpTimeConfig, PowerManager.BRIGHTNESS_MIN,
- PowerManager.BRIGHTNESS_MAX, dozeScaleFactor, lightSensorRate,
+ PowerManager.BRIGHTNESS_MAX, mDozeScaleFactor, lightSensorRate,
initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce,
brighteningLightDebounceIdle, darkeningLightDebounceIdle,
autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds,
@@ -1473,17 +1475,22 @@
mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
}
- // If there's an offload session and auto-brightness is on, we need to set the initial doze
- // brightness using the doze auto-brightness curve before the offload session starts
- // controlling the brightness.
- if (Float.isNaN(brightnessState) && mFlags.areAutoBrightnessModesEnabled()
- && mFlags.isDisplayOffloadEnabled()
- && mPowerRequest.policy == POLICY_DOZE
- && mDisplayOffloadSession != null
- && mAutomaticBrightnessController != null
- && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) {
- rawBrightnessState = mAutomaticBrightnessController
- .getAutomaticScreenBrightnessBasedOnLastObservedLux(mTempBrightnessEvent);
+ // If there's an offload session, we need to set the initial doze brightness before
+ // the offload session starts controlling the brightness.
+ if (Float.isNaN(brightnessState) && mFlags.isDisplayOffloadEnabled()
+ && mPowerRequest.policy == POLICY_DOZE && mDisplayOffloadSession != null) {
+ if (mAutomaticBrightnessController != null
+ && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) {
+ // Use the auto-brightness curve and the last observed lux
+ rawBrightnessState = mAutomaticBrightnessController
+ .getAutomaticScreenBrightnessBasedOnLastObservedLux(
+ mTempBrightnessEvent);
+ } else {
+ rawBrightnessState = getDozeBrightnessForOffload();
+ mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
+ | BrightnessEvent.FLAG_DOZE_SCALE);
+ }
+
if (BrightnessUtils.isValidBrightnessValue(rawBrightnessState)) {
brightnessState = clampScreenBrightness(rawBrightnessState);
mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_INITIAL);
@@ -2444,6 +2451,11 @@
}
@Override
+ public float getDozeBrightnessForOffload() {
+ return mDisplayBrightnessController.getCurrentBrightness() * mDozeScaleFactor;
+ }
+
+ @Override
public void setBrightness(float brightness) {
mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightness));
}
@@ -2596,6 +2608,7 @@
}
pw.println(" mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
pw.println(" mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
+ pw.println(" mDozeScaleFactor=" + mDozeScaleFactor);
mHandler.runWithScissors(() -> dumpLocal(pw), 1000);
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index ecf1635..408d610 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -161,6 +161,11 @@
float getScreenBrightnessSetting();
/**
+ * Gets the brightness value used when the device is in doze
+ */
+ float getDozeBrightnessForOffload();
+
+ /**
* Sets up the temporary brightness for the associated display
*/
void setTemporaryBrightness(float brightness);
diff --git a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
index ab7c503..a12d248 100644
--- a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
+++ b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
@@ -42,6 +42,9 @@
import com.android.server.display.notifications.DisplayNotificationManager;
import com.android.server.display.utils.DebugUtils;
+import java.util.HashSet;
+import java.util.Set;
+
/**
* Listens for Skin thermal sensor events, disables external displays if thermal status becomes
* equal or above {@link android.os.Temperature#THROTTLING_CRITICAL}, enables external displays if
@@ -106,6 +109,10 @@
private final ExternalDisplayStatsService mExternalDisplayStatsService;
@ThrottlingStatus
private volatile int mStatus = THROTTLING_NONE;
+ //@GuardedBy("mSyncRoot")
+ private boolean mIsBootCompleted;
+ //@GuardedBy("mSyncRoot")
+ private final Set<Integer> mDisplayIdsWaitingForBootCompletion = new HashSet<>();
ExternalDisplayPolicy(@NonNull final Injector injector) {
mInjector = injector;
@@ -121,6 +128,17 @@
* Starts listening for temperature changes.
*/
void onBootCompleted() {
+ synchronized (mSyncRoot) {
+ mIsBootCompleted = true;
+ for (var displayId : mDisplayIdsWaitingForBootCompletion) {
+ var logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayId);
+ if (logicalDisplay != null) {
+ handleExternalDisplayConnectedLocked(logicalDisplay);
+ }
+ }
+ mDisplayIdsWaitingForBootCompletion.clear();
+ }
+
if (!mFlags.isConnectedDisplayManagementEnabled()) {
if (DEBUG) {
Slog.d(TAG, "External display management is not enabled on your device:"
@@ -189,6 +207,11 @@
return;
}
+ if (!mIsBootCompleted) {
+ mDisplayIdsWaitingForBootCompletion.add(logicalDisplay.getDisplayIdLocked());
+ return;
+ }
+
mExternalDisplayStatsService.onDisplayConnected(logicalDisplay);
if ((Build.IS_ENG || Build.IS_USERDEBUG)
@@ -227,7 +250,12 @@
return;
}
- mExternalDisplayStatsService.onDisplayDisconnected(logicalDisplay.getDisplayIdLocked());
+ var displayId = logicalDisplay.getDisplayIdLocked();
+ if (mDisplayIdsWaitingForBootCompletion.remove(displayId)) {
+ return;
+ }
+
+ mExternalDisplayStatsService.onDisplayDisconnected(displayId);
}
/**
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index a9f78fd..47176fe 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -168,7 +168,7 @@
}
float getCurrentBrightnessMax() {
- if (!deviceSupportsHbm() || isCurrentlyAllowed()) {
+ if (!deviceSupportsHbm() || isHbmCurrentlyAllowed()) {
// Either the device doesn't support HBM, or HBM range is currently allowed (device
// it in a high-lux environment). In either case, return the highest brightness
// level supported by the device.
@@ -356,7 +356,7 @@
return event.getStartTimeMillis();
}
- private boolean isCurrentlyAllowed() {
+ boolean isHbmCurrentlyAllowed() {
// Returns true if HBM is allowed (above the ambient lux threshold) and there's still
// time within the current window for additional HBM usage. We return false if there is an
// HDR layer because we don't want the brightness MAX to change for HDR, which has its
@@ -369,7 +369,7 @@
&& !mIsBlockedByLowPowerMode);
}
- private boolean deviceSupportsHbm() {
+ boolean deviceSupportsHbm() {
return mHbmData != null && mHighBrightnessModeMetadata != null;
}
@@ -462,7 +462,7 @@
+ ", isOnlyAllowedToStayOn: " + isOnlyAllowedToStayOn
+ ", remainingAllowedTime: " + remainingTime
+ ", isLuxHigh: " + mIsInAllowedAmbientRange
- + ", isHBMCurrentlyAllowed: " + isCurrentlyAllowed()
+ + ", isHBMCurrentlyAllowed: " + isHbmCurrentlyAllowed()
+ ", isHdrLayerPresent: " + mIsHdrLayerPresent
+ ", mMaxDesiredHdrSdrRatio: " + mMaxDesiredHdrSdrRatio
+ ", isAutoBrightnessEnabled: " + mIsAutoBrightnessEnabled
@@ -575,7 +575,7 @@
return BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
} else if (mIsHdrLayerPresent) {
return BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR;
- } else if (isCurrentlyAllowed()) {
+ } else if (isHbmCurrentlyAllowed()) {
return BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 88c24e0..b2fd9ed 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -257,7 +257,8 @@
SurfaceControl.DynamicDisplayInfo dynamicInfo,
SurfaceControl.DesiredDisplayModeSpecs modeSpecs, boolean isFirstDisplay) {
super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + physicalDisplayId,
- getContext());
+ getContext(),
+ getFeatureFlags().isPixelAnisotropyCorrectionInLogicalDisplayEnabled());
mPhysicalDisplayId = physicalDisplayId;
mIsFirstDisplay = isFirstDisplay;
updateDisplayPropertiesLocked(staticDisplayInfo, dynamicInfo, modeSpecs);
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index db636d6..5eaaf35 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -35,7 +35,6 @@
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;
@@ -204,7 +203,28 @@
private SparseArray<SurfaceControl.RefreshRateRange> mThermalRefreshRateThrottling =
new SparseArray<>();
+ /**
+ * If the aspect ratio of the resolution of the display does not match the physical aspect
+ * ratio of the display, then without this feature enabled, picture would appear stretched to
+ * the user. This is because applications assume that they are rendered on square pixels
+ * (meaning density of pixels in x and y directions are equal). This would result into circles
+ * appearing as ellipses to the user.
+ * To compensate for non-square (anisotropic) pixels, if this feature is enabled:
+ * 1. LogicalDisplay will add more pixels for the applications to render on, as if the pixels
+ * were square and occupied the full display.
+ * 2. SurfaceFlinger will squeeze this taller/wider surface into the available number of
+ * physical pixels in the current display resolution.
+ * 3. If a setting on the display itself is set to "fill the entire display panel" then the
+ * display will stretch the pixels to fill the display fully.
+ */
+ private final boolean mIsAnisotropyCorrectionEnabled;
+
LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) {
+ this(displayId, layerStack, primaryDisplayDevice, false);
+ }
+
+ LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice,
+ boolean isAnisotropyCorrectionEnabled) {
mDisplayId = displayId;
mLayerStack = layerStack;
mPrimaryDisplayDevice = primaryDisplayDevice;
@@ -215,6 +235,7 @@
mThermalBrightnessThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
mPowerThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
mBaseDisplayInfo.thermalBrightnessThrottlingDataId = mThermalBrightnessThrottlingDataId;
+ mIsAnisotropyCorrectionEnabled = isAnisotropyCorrectionEnabled;
}
public void setDevicePositionLocked(int position) {
@@ -453,6 +474,14 @@
int maskedWidth = deviceInfo.width - maskingInsets.left - maskingInsets.right;
int maskedHeight = deviceInfo.height - maskingInsets.top - maskingInsets.bottom;
+ if (mIsAnisotropyCorrectionEnabled && deviceInfo.xDpi > 0 && deviceInfo.yDpi > 0) {
+ if (deviceInfo.xDpi > deviceInfo.yDpi * DisplayDevice.MAX_ANISOTROPY) {
+ maskedHeight = (int) (maskedHeight * deviceInfo.xDpi / deviceInfo.yDpi + 0.5);
+ } else if (deviceInfo.xDpi * DisplayDevice.MAX_ANISOTROPY < deviceInfo.yDpi) {
+ maskedWidth = (int) (maskedWidth * deviceInfo.yDpi / deviceInfo.xDpi + 0.5);
+ }
+ }
+
mBaseDisplayInfo.type = deviceInfo.type;
mBaseDisplayInfo.address = deviceInfo.address;
mBaseDisplayInfo.deviceProductInfo = deviceInfo.deviceProductInfo;
@@ -666,6 +695,31 @@
physWidth -= maskingInsets.left + maskingInsets.right;
physHeight -= maskingInsets.top + maskingInsets.bottom;
+ var displayLogicalWidth = displayInfo.logicalWidth;
+ var displayLogicalHeight = displayInfo.logicalHeight;
+
+ if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.xDpi > 0
+ && displayDeviceInfo.yDpi > 0) {
+ if (displayDeviceInfo.xDpi > displayDeviceInfo.yDpi * DisplayDevice.MAX_ANISOTROPY) {
+ var scalingFactor = displayDeviceInfo.yDpi / displayDeviceInfo.xDpi;
+ if (rotated) {
+ displayLogicalWidth = (int) ((float) displayLogicalWidth * scalingFactor + 0.5);
+ } else {
+ displayLogicalHeight = (int) ((float) displayLogicalHeight * scalingFactor
+ + 0.5);
+ }
+ } else if (displayDeviceInfo.xDpi * DisplayDevice.MAX_ANISOTROPY
+ < displayDeviceInfo.yDpi) {
+ var scalingFactor = displayDeviceInfo.xDpi / displayDeviceInfo.yDpi;
+ if (rotated) {
+ displayLogicalHeight = (int) ((float) displayLogicalHeight * scalingFactor
+ + 0.5);
+ } else {
+ displayLogicalWidth = (int) ((float) displayLogicalWidth * scalingFactor + 0.5);
+ }
+ }
+ }
+
// Determine whether the width or height is more constrained to be scaled.
// physWidth / displayInfo.logicalWidth => letter box
// or physHeight / displayInfo.logicalHeight => pillar box
@@ -675,16 +729,16 @@
// comparing them.
int displayRectWidth, displayRectHeight;
if ((displayInfo.flags & Display.FLAG_SCALING_DISABLED) != 0 || mDisplayScalingDisabled) {
- displayRectWidth = displayInfo.logicalWidth;
- displayRectHeight = displayInfo.logicalHeight;
- } else if (physWidth * displayInfo.logicalHeight
- < physHeight * displayInfo.logicalWidth) {
+ displayRectWidth = displayLogicalWidth;
+ displayRectHeight = displayLogicalHeight;
+ } else if (physWidth * displayLogicalHeight
+ < physHeight * displayLogicalWidth) {
// Letter box.
displayRectWidth = physWidth;
- displayRectHeight = displayInfo.logicalHeight * physWidth / displayInfo.logicalWidth;
+ displayRectHeight = displayLogicalHeight * physWidth / displayLogicalWidth;
} else {
// Pillar box.
- displayRectWidth = displayInfo.logicalWidth * physHeight / displayInfo.logicalHeight;
+ displayRectWidth = displayLogicalWidth * physHeight / displayLogicalHeight;
displayRectHeight = physHeight;
}
int displayRectTop = (physHeight - displayRectHeight) / 2;
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 2e8de31..e092fda 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -192,9 +192,10 @@
private final DisplayIdProducer mIdProducer = (isDefault) ->
isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++;
private Layout mCurrentLayout = null;
- private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
- private int mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
- private int mDeviceStateToBeAppliedAfterBoot = DeviceStateManager.INVALID_DEVICE_STATE;
+ private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
+ private int mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
+ private int mDeviceStateToBeAppliedAfterBoot =
+ DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
private boolean mBootCompleted = false;
private boolean mInteractive;
private final DisplayManagerFlags mFlags;
@@ -460,7 +461,7 @@
// temporarily turned off.
resetLayoutLocked(mDeviceState, state, /* transitionValue= */ true);
mPendingDeviceState = state;
- mDeviceStateToBeAppliedAfterBoot = DeviceStateManager.INVALID_DEVICE_STATE;
+ mDeviceStateToBeAppliedAfterBoot = DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
final boolean wakeDevice = shouldDeviceBeWoken(mPendingDeviceState, mDeviceState,
mInteractive, mBootCompleted);
final boolean sleepDevice = shouldDeviceBePutToSleep(mPendingDeviceState, mDeviceState,
@@ -510,7 +511,8 @@
void onBootCompleted() {
synchronized (mSyncRoot) {
mBootCompleted = true;
- if (mDeviceStateToBeAppliedAfterBoot != DeviceStateManager.INVALID_DEVICE_STATE) {
+ if (mDeviceStateToBeAppliedAfterBoot
+ != DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER) {
setDeviceStateLocked(mDeviceStateToBeAppliedAfterBoot,
/* isOverrideActive= */ false);
}
@@ -568,7 +570,7 @@
@VisibleForTesting
boolean shouldDeviceBePutToSleep(int pendingState, int currentState, boolean isOverrideActive,
boolean isInteractive, boolean isBootCompleted) {
- return currentState != DeviceStateManager.INVALID_DEVICE_STATE
+ return currentState != DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER
&& mDeviceStatesOnWhichToSleep.get(pendingState)
&& !mDeviceStatesOnWhichToSleep.get(currentState)
&& !isOverrideActive
@@ -598,13 +600,13 @@
private void transitionToPendingStateLocked() {
resetLayoutLocked(mDeviceState, mPendingDeviceState, /* transitionValue= */ false);
mDeviceState = mPendingDeviceState;
- mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
+ mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
applyLayoutLocked();
updateLogicalDisplaysLocked();
}
private void finishStateTransitionLocked(boolean force) {
- if (mPendingDeviceState == DeviceStateManager.INVALID_DEVICE_STATE) {
+ if (mPendingDeviceState == DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER) {
return;
}
@@ -1149,7 +1151,8 @@
*/
private LogicalDisplay createNewLogicalDisplayLocked(DisplayDevice device, int displayId) {
final int layerStack = assignLayerStackLocked(displayId);
- final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
+ final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device,
+ mFlags.isPixelAnisotropyCorrectionInLogicalDisplayEnabled());
display.updateLocked(mDisplayDeviceRepo);
final DisplayInfo info = display.getDisplayInfoLocked();
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
index 71cc872..03fb147 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -71,8 +71,14 @@
@Nullable
private final OffloadBrightnessStrategy mOffloadBrightnessStrategy;
+ // A collective representation of all the strategies that the selector is aware of. This is
+ // non null, but the strategies this is tracking can be null
+ @NonNull
private final DisplayBrightnessStrategy[] mDisplayBrightnessStrategies;
+ @NonNull
+ private final DisplayManagerFlags mDisplayManagerFlags;
+
// We take note of the old brightness strategy so that we can know when the strategy changes.
private String mOldBrightnessStrategyName;
@@ -86,6 +92,7 @@
if (injector == null) {
injector = new Injector();
}
+ mDisplayManagerFlags = flags;
mDisplayId = displayId;
mDozeBrightnessStrategy = injector.getDozeBrightnessStrategy();
mScreenOffBrightnessStrategy = injector.getScreenOffBrightnessStrategy();
@@ -139,6 +146,10 @@
displayBrightnessStrategy = mOffloadBrightnessStrategy;
}
+ if (mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) {
+ postProcess(constructStrategySelectionNotifyRequest(displayBrightnessStrategy));
+ }
+
if (!mOldBrightnessStrategyName.equals(displayBrightnessStrategy.getName())) {
Slog.i(TAG,
"Changing the DisplayBrightnessStrategy from " + mOldBrightnessStrategyName
@@ -186,13 +197,27 @@
" mAllowAutoBrightnessWhileDozingConfig= "
+ mAllowAutoBrightnessWhileDozingConfig);
IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " ");
- for (DisplayBrightnessStrategy displayBrightnessStrategy: mDisplayBrightnessStrategies) {
+ for (DisplayBrightnessStrategy displayBrightnessStrategy : mDisplayBrightnessStrategies) {
if (displayBrightnessStrategy != null) {
displayBrightnessStrategy.dump(ipw);
}
}
}
+ private StrategySelectionNotifyRequest constructStrategySelectionNotifyRequest(
+ DisplayBrightnessStrategy selectedDisplayBrightnessStrategy) {
+ return new StrategySelectionNotifyRequest(selectedDisplayBrightnessStrategy);
+ }
+
+ private void postProcess(StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+ for (DisplayBrightnessStrategy displayBrightnessStrategy : mDisplayBrightnessStrategies) {
+ if (displayBrightnessStrategy != null) {
+ displayBrightnessStrategy.strategySelectionPostProcessor(
+ strategySelectionNotifyRequest);
+ }
+ }
+ }
+
/**
* Validates if the conditions are met to qualify for the DozeBrightnessStrategy.
*/
diff --git a/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java b/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java
new file mode 100644
index 0000000..d8bd2e4
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness;
+
+import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
+
+import java.util.Objects;
+
+/**
+ * A wrapper class to encapsulate the request to notify the strategies about the selection of a
+ * DisplayBrightnessStrategy
+ */
+public final class StrategySelectionNotifyRequest {
+ // The strategy that was selected with the current request
+ private final DisplayBrightnessStrategy mSelectedDisplayBrightnessStrategy;
+
+ public StrategySelectionNotifyRequest(DisplayBrightnessStrategy displayBrightnessStrategy) {
+ mSelectedDisplayBrightnessStrategy = displayBrightnessStrategy;
+ }
+
+ public DisplayBrightnessStrategy getSelectedDisplayBrightnessStrategy() {
+ return mSelectedDisplayBrightnessStrategy;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof StrategySelectionNotifyRequest)) {
+ return false;
+ }
+ StrategySelectionNotifyRequest other = (StrategySelectionNotifyRequest) obj;
+ return other.getSelectedDisplayBrightnessStrategy()
+ == getSelectedDisplayBrightnessStrategy();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSelectedDisplayBrightnessStrategy);
+ }
+}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java
index 9ee1d73..11edde9 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java
@@ -22,6 +22,7 @@
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
import java.io.PrintWriter;
@@ -53,4 +54,10 @@
@Override
public void dump(PrintWriter writer) {}
+
+ @Override
+ public void strategySelectionPostProcessor(
+ StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+ // DO NOTHING
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java
index 1f28eb4..7b49957 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java
@@ -20,6 +20,7 @@
import android.hardware.display.DisplayManagerInternal;
import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
import java.io.PrintWriter;
@@ -48,4 +49,10 @@
* @param writer
*/
void dump(PrintWriter writer);
+
+ /**
+ * Notifies this strategy about the selection of a DisplayBrightnessStrategy
+ */
+ void strategySelectionPostProcessor(
+ StrategySelectionNotifyRequest strategySelectionNotifyRequest);
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java
index 2be7443..5afdc42 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java
@@ -21,6 +21,7 @@
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
import java.io.PrintWriter;
@@ -46,4 +47,10 @@
@Override
public void dump(PrintWriter writer) {}
+
+ @Override
+ public void strategySelectionPostProcessor(
+ StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+ // DO NOTHING
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
index 54f9afc..0650c1c 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
@@ -22,6 +22,7 @@
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
import java.io.PrintWriter;
@@ -82,4 +83,10 @@
writer.println(" mBrightnessToFollow:" + mBrightnessToFollow);
writer.println(" mBrightnessToFollowSlowChange:" + mBrightnessToFollowSlowChange);
}
+
+ @Override
+ public void strategySelectionPostProcessor(
+ StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+ // DO NOTHING
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java
index 49c3e03..bf37ee0 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java
@@ -22,6 +22,7 @@
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
import java.io.PrintWriter;
@@ -44,4 +45,10 @@
@Override
public void dump(PrintWriter writer) {}
+
+ @Override
+ public void strategySelectionPostProcessor(
+ StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+ // DO NOTHING
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java
index 4ffb16b..d2bb1e2 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java
@@ -21,6 +21,7 @@
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
import java.io.PrintWriter;
@@ -72,4 +73,10 @@
writer.println("OffloadBrightnessStrategy:");
writer.println(" mOffloadScreenBrightness:" + mOffloadScreenBrightness);
}
+
+ @Override
+ public void strategySelectionPostProcessor(
+ StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+ // DO NOTHING
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
index 7b651d8..653170c 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
@@ -21,6 +21,7 @@
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
import java.io.PrintWriter;
@@ -45,4 +46,10 @@
@Override
public void dump(PrintWriter writer) {}
+
+ @Override
+ public void strategySelectionPostProcessor(
+ StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+ // DO NOTHING
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java
index 201ef41..f0cce23 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java
@@ -22,6 +22,7 @@
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
import java.io.PrintWriter;
@@ -46,4 +47,10 @@
@Override
public void dump(PrintWriter writer) {}
+
+ @Override
+ public void strategySelectionPostProcessor(
+ StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+ // DO NOTHING
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java
index bbd0c00..91e1d09 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java
@@ -22,6 +22,7 @@
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
import java.io.PrintWriter;
@@ -73,4 +74,10 @@
writer.println("TemporaryBrightnessStrategy:");
writer.println(" mTemporaryScreenBrightness:" + mTemporaryScreenBrightness);
}
+
+ @Override
+ public void strategySelectionPostProcessor(
+ StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+ // DO NOTHING
+ }
}
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 516d4b1..15ee937 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -121,11 +121,23 @@
Flags::refreshRateVotingTelemetry
);
+ private final FlagState mPixelAnisotropyCorrectionEnabled = new FlagState(
+ Flags.FLAG_ENABLE_PIXEL_ANISOTROPY_CORRECTION,
+ Flags::enablePixelAnisotropyCorrection
+ );
+
private final FlagState mSensorBasedBrightnessThrottling = new FlagState(
Flags.FLAG_SENSOR_BASED_BRIGHTNESS_THROTTLING,
Flags::sensorBasedBrightnessThrottling
);
+
+ private final FlagState mRefactorDisplayPowerController = new FlagState(
+ Flags.FLAG_REFACTOR_DISPLAY_POWER_CONTROLLER,
+ Flags::refactorDisplayPowerController
+ );
+
+
/**
* @return {@code true} if 'port' is allowed in display layout configuration file.
*/
@@ -252,10 +264,18 @@
return mRefreshRateVotingTelemetry.isEnabled();
}
+ public boolean isPixelAnisotropyCorrectionInLogicalDisplayEnabled() {
+ return mPixelAnisotropyCorrectionEnabled.isEnabled();
+ }
+
public boolean isSensorBasedBrightnessThrottlingEnabled() {
return mSensorBasedBrightnessThrottling.isEnabled();
}
+ public boolean isRefactorDisplayPowerControllerEnabled() {
+ return mRefactorDisplayPowerController.isEnabled();
+ }
+
/**
* dumps all flagstates
* @param pw printWriter
@@ -279,7 +299,9 @@
pw.println(" " + mAutoBrightnessModesFlagState);
pw.println(" " + mFastHdrTransitions);
pw.println(" " + mRefreshRateVotingTelemetry);
+ pw.println(" " + mPixelAnisotropyCorrectionEnabled);
pw.println(" " + mSensorBasedBrightnessThrottling);
+ pw.println(" " + mRefactorDisplayPowerController);
}
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 63ab3a9..9bf36e4 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
@@ -186,9 +186,25 @@
}
flag {
+ name: "enable_pixel_anisotropy_correction"
+ namespace: "display_manager"
+ description: "Feature flag for enabling display anisotropy correction through LogicalDisplay upscaling"
+ bug: "317363416"
+ is_fixed_read_only: true
+}
+
+flag {
name: "sensor_based_brightness_throttling"
namespace: "display_manager"
description: "Feature flag for enabling brightness throttling using sensor from config."
bug: "294900859"
is_fixed_read_only: true
}
+
+flag {
+ name: "refactor_display_power_controller"
+ namespace: "display_manager"
+ description: "Feature flag for refactoring the DisplayPowerController and associated components"
+ bug: "294444204"
+ is_fixed_read_only: true
+}
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 8b4e1ff..64cbd54 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -1015,12 +1015,15 @@
// Infinity means that we want the highest possible refresh rate
minRefreshRate = highestRefreshRate;
- if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled) {
- // The flag had been turned off, we need to restore the original value
+ if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
+ && displayId == Display.DEFAULT_DISPLAY) {
+ // The flag has been turned off, we need to restore the original value. We'll
+ // use the peak refresh rate of the default display.
Settings.System.putFloatForUser(cr, Settings.System.MIN_REFRESH_RATE,
highestRefreshRate, cr.getUserId());
}
} else if (mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
+ && displayId == Display.DEFAULT_DISPLAY
&& Math.round(minRefreshRate) == Math.round(highestRefreshRate)) {
// The flag has been turned on, we need to upgrade the setting
Settings.System.putFloatForUser(cr, Settings.System.MIN_REFRESH_RATE,
@@ -1033,12 +1036,15 @@
// Infinity means that we want the highest possible refresh rate
peakRefreshRate = highestRefreshRate;
- if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled) {
- // The flag had been turned off, we need to restore the original value
+ if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
+ && displayId == Display.DEFAULT_DISPLAY) {
+ // The flag has been turned off, we need to restore the original value. We'll
+ // use the peak refresh rate of the default display.
Settings.System.putFloatForUser(cr, Settings.System.PEAK_REFRESH_RATE,
highestRefreshRate, cr.getUserId());
}
} else if (mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
+ && displayId == Display.DEFAULT_DISPLAY
&& Math.round(peakRefreshRate) == Math.round(highestRefreshRate)) {
// The flag has been turned on, we need to upgrade the setting
Settings.System.putFloatForUser(cr, Settings.System.PEAK_REFRESH_RATE,
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index c6d66db..d997020 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -982,6 +982,18 @@
}
@Override // Binder call
+ public boolean canStartDreaming(boolean isScreenOn) {
+ checkPermission(android.Manifest.permission.READ_DREAM_STATE);
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return canStartDreamingInternal(isScreenOn);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
public void testDream(int userId, ComponentName dream) {
if (dream == null) {
throw new IllegalArgumentException("dream must not be null");
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index 252ea4b..3fafca8 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -61,7 +61,6 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.InputStream;
import java.nio.charset.StandardCharsets;
/**
@@ -126,14 +125,18 @@
@Override
public void setSystemWideGrammaticalGender(int grammaticalGender, int userId) {
- checkCallerIsSystem();
+ isCallerAllowed();
GrammaticalInflectionService.this.setSystemWideGrammaticalGender(grammaticalGender,
userId);
}
@Override
public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
- return canGetSystemGrammaticalGender(attributionSource)
+ if (!checkSystemGrammaticalGenderPermission(mPermissionManager, attributionSource)) {
+ throw new SecurityException("AttributionSource: " + attributionSource
+ + " does not have READ_SYSTEM_GRAMMATICAL_GENDER permission.");
+ }
+ return checkSystemTermsOfAddressIsEnabled()
? GrammaticalInflectionService.this.getSystemGrammaticalGender(
attributionSource, userId)
: GRAMMATICAL_GENDER_NOT_SPECIFIED;
@@ -154,7 +157,7 @@
@Override
@Nullable
public byte[] getBackupPayload(int userId) {
- checkCallerIsSystem();
+ isCallerAllowed();
return mBackupHelper.getBackupPayload(userId);
}
@@ -263,7 +266,11 @@
throw new RuntimeException(e);
}
}
+ updateConfiguration(grammaticalGender, userId);
+ Trace.endSection();
+ }
+ private void updateConfiguration(int grammaticalGender, int userId) {
try {
Configuration config = new Configuration();
int preValue = config.getGrammaticalGender();
@@ -277,7 +284,6 @@
} catch (RemoteException e) {
Log.w(TAG, "Can not update configuration", e);
}
- Trace.endSection();
}
public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
@@ -333,11 +339,13 @@
return GRAMMATICAL_GENDER_NOT_SPECIFIED;
}
- private void checkCallerIsSystem() {
+ private void isCallerAllowed() {
int callingUid = Binder.getCallingUid();
if (callingUid != Process.SYSTEM_UID && callingUid != Process.SHELL_UID
&& callingUid != Process.ROOT_UID) {
- throw new SecurityException("Caller is not system, shell and root.");
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CHANGE_CONFIGURATION,
+ "Caller must be system, shell, root or has CHANGE_CONFIGURATION permission.");
}
}
@@ -367,7 +375,9 @@
if (mGrammaticalGenderCache.indexOfKey(userId) < 0) {
try (FileInputStream in = new FileInputStream(file)) {
final TypedXmlPullParser parser = Xml.resolvePullParser(in);
- mGrammaticalGenderCache.put(userId, getGrammaticalGenderFromXml(parser));
+ int grammaticalGender = getGrammaticalGenderFromXml(parser);
+ mGrammaticalGenderCache.put(userId, grammaticalGender);
+ updateConfiguration(grammaticalGender, userId);
} catch (IOException | XmlPullParserException e) {
Log.e(TAG, "Failed to parse XML configuration from " + file, e);
}
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index c02d524..a1341b7 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -258,6 +258,7 @@
}
private void updateStylusPointerIconEnabled() {
- mNative.setStylusPointerIconEnabled(InputSettings.isStylusPointerIconEnabled(mContext));
+ mNative.setStylusPointerIconEnabled(
+ InputSettings.isStylusPointerIconEnabled(mContext, true /* forceReloadSetting */));
}
}
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
new file mode 100644
index 0000000..dd6433d
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.Handler;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+/**
+ * Provides accesses to per-user additional {@link android.view.inputmethod.InputMethodSubtype}
+ * persistent storages.
+ */
+final class AdditionalSubtypeMapRepository {
+ @GuardedBy("ImfLock.class")
+ @NonNull
+ private static final SparseArray<AdditionalSubtypeMap> sPerUserMap = new SparseArray<>();
+
+ /**
+ * Not intended to be instantiated.
+ */
+ private AdditionalSubtypeMapRepository() {
+ }
+
+ @NonNull
+ @GuardedBy("ImfLock.class")
+ static AdditionalSubtypeMap get(@UserIdInt int userId) {
+ final AdditionalSubtypeMap map = sPerUserMap.get(userId);
+ if (map != null) {
+ return map;
+ }
+ final AdditionalSubtypeMap newMap = AdditionalSubtypeUtils.load(userId);
+ sPerUserMap.put(userId, newMap);
+ return newMap;
+ }
+
+ @GuardedBy("ImfLock.class")
+ static void putAndSave(@UserIdInt int userId, @NonNull AdditionalSubtypeMap map,
+ @NonNull InputMethodMap inputMethodMap) {
+ final AdditionalSubtypeMap previous = sPerUserMap.get(userId);
+ if (previous == map) {
+ return;
+ }
+ sPerUserMap.put(userId, map);
+ // TODO: Offload this to a background thread.
+ // TODO: Skip if the previous data is exactly the same as new one.
+ AdditionalSubtypeUtils.save(map, inputMethodMap, userId);
+ }
+
+ static void initialize(@NonNull Handler handler, @NonNull Context context) {
+ final UserManagerInternal userManagerInternal =
+ LocalServices.getService(UserManagerInternal.class);
+ handler.post(() -> {
+ userManagerInternal.addUserLifecycleListener(
+ new UserManagerInternal.UserLifecycleListener() {
+ @Override
+ public void onUserCreated(UserInfo user, @Nullable Object token) {
+ final int userId = user.id;
+ handler.post(() -> {
+ synchronized (ImfLock.class) {
+ if (!sPerUserMap.contains(userId)) {
+ final AdditionalSubtypeMap additionalSubtypeMap =
+ AdditionalSubtypeUtils.load(userId);
+ sPerUserMap.put(userId, additionalSubtypeMap);
+ final InputMethodSettings settings =
+ InputMethodManagerService
+ .queryInputMethodServicesInternal(context,
+ userId,
+ additionalSubtypeMap,
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, settings);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onUserRemoved(UserInfo user) {
+ final int userId = user.id;
+ handler.post(() -> {
+ synchronized (ImfLock.class) {
+ sPerUserMap.remove(userId);
+ }
+ });
+ }
+ });
+ synchronized (ImfLock.class) {
+ for (int userId : userManagerInternal.getUserIds()) {
+ sPerUserMap.put(userId, AdditionalSubtypeUtils.load(userId));
+ }
+ }
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 2205986..a0e910d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -67,7 +67,6 @@
import android.annotation.Nullable;
import android.annotation.UiThread;
import android.annotation.UserIdInt;
-import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -192,23 +191,20 @@
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
+import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
import java.security.InvalidParameterException;
-import java.time.Instant;
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import java.util.Locale;
import java.util.Objects;
import java.util.OptionalInt;
import java.util.WeakHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
@@ -230,6 +226,16 @@
int FAILURE = -1;
}
+ /**
+ * Indicates that the annotated field is not yet ready for concurrent multi-user support.
+ *
+ * <p>See b/305849394 for details.</p>
+ */
+ @Retention(SOURCE)
+ @Target({ElementType.FIELD})
+ private @interface MultiUserUnawareField {
+ }
+
private static final int MSG_SHOW_IM_SUBTYPE_PICKER = 1;
private static final int MSG_HIDE_ALL_INPUT_METHODS = 1035;
@@ -278,8 +284,14 @@
final Context mContext;
final Resources mRes;
private final Handler mHandler;
+
+ /**
+ * TODO(b/329163064): Remove this field.
+ */
@NonNull
+ @MultiUserUnawareField
private InputMethodSettings mSettings;
+ @MultiUserUnawareField
final SettingsObserver mSettingsObserver;
final WindowManagerInternal mWindowManagerInternal;
private final ActivityManagerInternal mActivityManagerInternal;
@@ -288,15 +300,16 @@
final ImePlatformCompatUtils mImePlatformCompatUtils;
final InputMethodDeviceConfigs mInputMethodDeviceConfigs;
- @GuardedBy("ImfLock.class")
- @NonNull
- private AdditionalSubtypeMap mAdditionalSubtypeMap = AdditionalSubtypeMap.EMPTY_MAP;
private final UserManagerInternal mUserManagerInternal;
+ @MultiUserUnawareField
private final InputMethodMenuController mMenuController;
+ @MultiUserUnawareField
@NonNull private final InputMethodBindingController mBindingController;
+ @MultiUserUnawareField
@NonNull private final AutofillSuggestionsController mAutofillController;
@GuardedBy("ImfLock.class")
+ @MultiUserUnawareField
@NonNull private final ImeVisibilityStateComputer mVisibilityStateComputer;
@GuardedBy("ImfLock.class")
@@ -315,13 +328,16 @@
// Mapping from deviceId to the device-specific imeId for that device.
@GuardedBy("ImfLock.class")
+ @MultiUserUnawareField
private final SparseArray<String> mVirtualDeviceMethodMap = new SparseArray<>();
// TODO: Instantiate mSwitchingController for each user.
@NonNull
+ @MultiUserUnawareField
private InputMethodSubtypeSwitchingController mSwitchingController;
// TODO: Instantiate mHardwareKeyboardShortcutController for each user.
@NonNull
+ @MultiUserUnawareField
private HardwareKeyboardShortcutController mHardwareKeyboardShortcutController;
/**
@@ -339,23 +355,29 @@
}
@GuardedBy("ImfLock.class")
+ @MultiUserUnawareField
private int mDisplayIdToShowIme = INVALID_DISPLAY;
@GuardedBy("ImfLock.class")
+ @MultiUserUnawareField
private int mDeviceIdToShowIme = DEVICE_ID_DEFAULT;
@Nullable private StatusBarManagerInternal mStatusBarManagerInternal;
private boolean mShowOngoingImeSwitcherForPhones;
@GuardedBy("ImfLock.class")
+ @MultiUserUnawareField
private final HandwritingModeController mHwController;
@GuardedBy("ImfLock.class")
+ @MultiUserUnawareField
private IntArray mStylusIds;
@GuardedBy("ImfLock.class")
@Nullable
+ @MultiUserUnawareField
private OverlayableSystemBooleanResourceWrapper mImeDrawsImeNavBarRes;
@GuardedBy("ImfLock.class")
@Nullable
+ @MultiUserUnawareField
Future<?> mImeDrawsImeNavBarResLazyInitFuture;
static class SessionState {
@@ -420,6 +442,7 @@
/**
* Holds the current IME binding state info.
*/
+ @MultiUserUnawareField
ImeBindingState mImeBindingState;
/**
@@ -486,27 +509,32 @@
* upon reports from the input method. If the window state is already changed before the report
* is handled, this field just keeps the last value.
*/
+ @MultiUserUnawareField
IBinder mLastImeTargetWindow;
/**
* The {@link IRemoteInputConnection} last provided by the current client.
*/
+ @MultiUserUnawareField
IRemoteInputConnection mCurInputConnection;
/**
* The {@link ImeOnBackInvokedDispatcher} last provided by the current client to
* receive {@link android.window.OnBackInvokedCallback}s forwarded from IME.
*/
+ @MultiUserUnawareField
ImeOnBackInvokedDispatcher mCurImeDispatcher;
/**
* The {@link IRemoteAccessibilityInputConnection} last provided by the current client.
*/
+ @MultiUserUnawareField
@Nullable IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection;
/**
* The {@link EditorInfo} last provided by the current client.
*/
+ @MultiUserUnawareField
@Nullable
EditorInfo mCurEditorInfo;
@@ -527,11 +555,13 @@
/**
* The current subtype of the current input method.
*/
+ @MultiUserUnawareField
private InputMethodSubtype mCurrentSubtype;
/**
* {@code true} if the IME has not been mostly hidden via {@link android.view.InsetsController}
*/
+ @MultiUserUnawareField
private boolean mCurPerceptible;
/**
@@ -548,11 +578,13 @@
* otherwise {@code null}.
*/
@Nullable
+ @MultiUserUnawareField
private ImeTracker.Token mCurStatsToken;
/**
* {@code true} if the current input method is in fullscreen mode.
*/
+ @MultiUserUnawareField
boolean mInFullscreenMode;
/**
@@ -588,6 +620,7 @@
}
@GuardedBy("ImfLock.class")
+ @MultiUserUnawareField
private int mCurTokenDisplayId = INVALID_DISPLAY;
/**
@@ -595,6 +628,7 @@
*/
@GuardedBy("ImfLock.class")
@Nullable
+ @MultiUserUnawareField
private IBinder mCurHostInputToken;
/**
@@ -633,25 +667,31 @@
/**
* Have we called mCurMethod.bindInput()?
*/
+ @MultiUserUnawareField
boolean mBoundToMethod;
/**
* Have we called bindInput() for accessibility services?
*/
+ @MultiUserUnawareField
boolean mBoundToAccessibility;
/**
* Currently enabled session.
*/
@GuardedBy("ImfLock.class")
+ @MultiUserUnawareField
SessionState mEnabledSession;
+ @MultiUserUnawareField
SparseArray<AccessibilitySessionState> mEnabledAccessibilitySessions = new SparseArray<>();
/**
* True if the device is currently interactive with user. The value is true initially.
*/
+ @MultiUserUnawareField
boolean mIsInteractive = true;
+ @MultiUserUnawareField
int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
/**
@@ -675,6 +715,7 @@
* <em>Do not update this value outside of {@link #setImeWindowStatus(IBinder, int, int)} and
* {@link InputMethodBindingController#unbindCurrentMethod()}.</em>
*/
+ @MultiUserUnawareField
int mImeWindowVis;
private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
@@ -687,344 +728,9 @@
private final CopyOnWriteArrayList<InputMethodListListener> mInputMethodListListeners =
new CopyOnWriteArrayList<>();
- /**
- * Internal state snapshot when
- * {@link IInputMethod#startInput(IInputMethod.StartInputParams)} is about to be called.
- *
- * <p>Calling that IPC endpoint basically means that
- * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
- * back in the current IME process shortly, which will also affect what the current IME starts
- * receiving from {@link InputMethodService#getCurrentInputConnection()}. In other words, this
- * snapshot will be taken every time when {@link InputMethodManagerService} is initiating a new
- * logical input session between the client application and the current IME.</p>
- *
- * <p>Be careful to not keep strong references to this object forever, which can prevent
- * {@link StartInputInfo#mImeToken} and {@link StartInputInfo#mTargetWindow} from being GC-ed.
- * </p>
- */
- private static class StartInputInfo {
- private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
-
- final int mSequenceNumber;
- final long mTimestamp;
- final long mWallTime;
- @UserIdInt
- final int mImeUserId;
- @NonNull
- final IBinder mImeToken;
- final int mImeDisplayId;
- @NonNull
- final String mImeId;
- @StartInputReason
- final int mStartInputReason;
- final boolean mRestarting;
- @UserIdInt
- final int mTargetUserId;
- final int mTargetDisplayId;
- @Nullable
- final IBinder mTargetWindow;
- @NonNull
- final EditorInfo mEditorInfo;
- @SoftInputModeFlags
- final int mTargetWindowSoftInputMode;
- final int mClientBindSequenceNumber;
-
- StartInputInfo(@UserIdInt int imeUserId, @NonNull IBinder imeToken, int imeDisplayId,
- @NonNull String imeId, @StartInputReason int startInputReason, boolean restarting,
- @UserIdInt int targetUserId, int targetDisplayId, @Nullable IBinder targetWindow,
- @NonNull EditorInfo editorInfo, @SoftInputModeFlags int targetWindowSoftInputMode,
- int clientBindSequenceNumber) {
- mSequenceNumber = sSequenceNumber.getAndIncrement();
- mTimestamp = SystemClock.uptimeMillis();
- mWallTime = System.currentTimeMillis();
- mImeUserId = imeUserId;
- mImeToken = imeToken;
- mImeDisplayId = imeDisplayId;
- mImeId = imeId;
- mStartInputReason = startInputReason;
- mRestarting = restarting;
- mTargetUserId = targetUserId;
- mTargetDisplayId = targetDisplayId;
- mTargetWindow = targetWindow;
- mEditorInfo = editorInfo;
- mTargetWindowSoftInputMode = targetWindowSoftInputMode;
- mClientBindSequenceNumber = clientBindSequenceNumber;
- }
- }
-
@GuardedBy("ImfLock.class")
private final WeakHashMap<IBinder, IBinder> mImeTargetWindowMap = new WeakHashMap<>();
- @VisibleForTesting
- static final class SoftInputShowHideHistory {
- private final Entry[] mEntries = new Entry[16];
- private int mNextIndex = 0;
- private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
-
- static final class Entry {
- final int mSequenceNumber = sSequenceNumber.getAndIncrement();
- @Nullable
- final ClientState mClientState;
- @SoftInputModeFlags
- final int mFocusedWindowSoftInputMode;
- @SoftInputShowHideReason
- final int mReason;
- // The timing of handling showCurrentInputLocked() or hideCurrentInputLocked().
- final long mTimestamp;
- final long mWallTime;
- final boolean mInFullscreenMode;
- @NonNull
- final String mFocusedWindowName;
- @Nullable
- final EditorInfo mEditorInfo;
- @NonNull
- final String mRequestWindowName;
- @Nullable
- final String mImeControlTargetName;
- @Nullable
- final String mImeTargetNameFromWm;
- @Nullable
- final String mImeSurfaceParentName;
-
- Entry(ClientState client, EditorInfo editorInfo,
- String focusedWindowName, @SoftInputModeFlags int softInputMode,
- @SoftInputShowHideReason int reason,
- boolean inFullscreenMode, String requestWindowName,
- @Nullable String imeControlTargetName, @Nullable String imeTargetName,
- @Nullable String imeSurfaceParentName) {
- mClientState = client;
- mEditorInfo = editorInfo;
- mFocusedWindowName = focusedWindowName;
- mFocusedWindowSoftInputMode = softInputMode;
- mReason = reason;
- mTimestamp = SystemClock.uptimeMillis();
- mWallTime = System.currentTimeMillis();
- mInFullscreenMode = inFullscreenMode;
- mRequestWindowName = requestWindowName;
- mImeControlTargetName = imeControlTargetName;
- mImeTargetNameFromWm = imeTargetName;
- mImeSurfaceParentName = imeSurfaceParentName;
- }
- }
-
- void addEntry(@NonNull Entry entry) {
- final int index = mNextIndex;
- mEntries[index] = entry;
- mNextIndex = (mNextIndex + 1) % mEntries.length;
- }
-
- void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
- final DateTimeFormatter formatter =
- DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
- .withZone(ZoneId.systemDefault());
-
- for (int i = 0; i < mEntries.length; ++i) {
- final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
- if (entry == null) {
- continue;
- }
- pw.print(prefix);
- pw.println("SoftInputShowHide #" + entry.mSequenceNumber + ":");
-
- pw.print(prefix);
- pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
- + " (timestamp=" + entry.mTimestamp + ")");
-
- pw.print(prefix);
- pw.print(" reason=" + InputMethodDebug.softInputDisplayReasonToString(
- entry.mReason));
- pw.println(" inFullscreenMode=" + entry.mInFullscreenMode);
-
- pw.print(prefix);
- pw.println(" requestClient=" + entry.mClientState);
-
- pw.print(prefix);
- pw.println(" focusedWindowName=" + entry.mFocusedWindowName);
-
- pw.print(prefix);
- pw.println(" requestWindowName=" + entry.mRequestWindowName);
-
- pw.print(prefix);
- pw.println(" imeControlTargetName=" + entry.mImeControlTargetName);
-
- pw.print(prefix);
- pw.println(" imeTargetNameFromWm=" + entry.mImeTargetNameFromWm);
-
- pw.print(prefix);
- pw.println(" imeSurfaceParentName=" + entry.mImeSurfaceParentName);
-
- pw.print(prefix);
- pw.print(" editorInfo:");
- if (entry.mEditorInfo != null) {
- pw.print(" inputType=" + entry.mEditorInfo.inputType);
- pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
- pw.println(" fieldId (viewId)=" + entry.mEditorInfo.fieldId);
- } else {
- pw.println(" null");
- }
-
- pw.print(prefix);
- pw.println(" focusedWindowSoftInputMode=" + InputMethodDebug.softInputModeToString(
- entry.mFocusedWindowSoftInputMode));
- }
- }
- }
-
- /**
- * A ring buffer to store the history of {@link StartInputInfo}.
- */
- private static final class StartInputHistory {
- /**
- * Entry size for non low-RAM devices.
- *
- * <p>TODO: Consider to follow what other system services have been doing to manage
- * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
- */
- private static final int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 32;
-
- /**
- * Entry size for low-RAM devices.
- *
- * <p>TODO: Consider to follow what other system services have been doing to manage
- * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
- */
- private static final int ENTRY_SIZE_FOR_LOW_RAM_DEVICE = 5;
-
- private static int getEntrySize() {
- if (ActivityManager.isLowRamDeviceStatic()) {
- return ENTRY_SIZE_FOR_LOW_RAM_DEVICE;
- } else {
- return ENTRY_SIZE_FOR_HIGH_RAM_DEVICE;
- }
- }
-
- /**
- * Backing store for the ring buffer.
- */
- private final Entry[] mEntries = new Entry[getEntrySize()];
-
- /**
- * An index of {@link #mEntries}, to which next {@link #addEntry(StartInputInfo)} should
- * write.
- */
- private int mNextIndex = 0;
-
- /**
- * Recyclable entry to store the information in {@link StartInputInfo}.
- */
- private static final class Entry {
- int mSequenceNumber;
- long mTimestamp;
- long mWallTime;
- @UserIdInt
- int mImeUserId;
- @NonNull
- String mImeTokenString;
- int mImeDisplayId;
- @NonNull
- String mImeId;
- @StartInputReason
- int mStartInputReason;
- boolean mRestarting;
- @UserIdInt
- int mTargetUserId;
- int mTargetDisplayId;
- @NonNull
- String mTargetWindowString;
- @NonNull
- EditorInfo mEditorInfo;
- @SoftInputModeFlags
- int mTargetWindowSoftInputMode;
- int mClientBindSequenceNumber;
-
- Entry(@NonNull StartInputInfo original) {
- set(original);
- }
-
- void set(@NonNull StartInputInfo original) {
- mSequenceNumber = original.mSequenceNumber;
- mTimestamp = original.mTimestamp;
- mWallTime = original.mWallTime;
- mImeUserId = original.mImeUserId;
- // Intentionally convert to String so as not to keep a strong reference to a Binder
- // object.
- mImeTokenString = String.valueOf(original.mImeToken);
- mImeDisplayId = original.mImeDisplayId;
- mImeId = original.mImeId;
- mStartInputReason = original.mStartInputReason;
- mRestarting = original.mRestarting;
- mTargetUserId = original.mTargetUserId;
- mTargetDisplayId = original.mTargetDisplayId;
- // Intentionally convert to String so as not to keep a strong reference to a Binder
- // object.
- mTargetWindowString = String.valueOf(original.mTargetWindow);
- mEditorInfo = original.mEditorInfo;
- mTargetWindowSoftInputMode = original.mTargetWindowSoftInputMode;
- mClientBindSequenceNumber = original.mClientBindSequenceNumber;
- }
- }
-
- /**
- * Add a new entry and discard the oldest entry as needed.
- * @param info {@link StartInputInfo} to be added.
- */
- void addEntry(@NonNull StartInputInfo info) {
- final int index = mNextIndex;
- if (mEntries[index] == null) {
- mEntries[index] = new Entry(info);
- } else {
- mEntries[index].set(info);
- }
- mNextIndex = (mNextIndex + 1) % mEntries.length;
- }
-
- void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
- final DateTimeFormatter formatter =
- DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
- .withZone(ZoneId.systemDefault());
-
- for (int i = 0; i < mEntries.length; ++i) {
- final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
- if (entry == null) {
- continue;
- }
- pw.print(prefix);
- pw.println("StartInput #" + entry.mSequenceNumber + ":");
-
- pw.print(prefix);
- pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
- + " (timestamp=" + entry.mTimestamp + ")"
- + " reason="
- + InputMethodDebug.startInputReasonToString(entry.mStartInputReason)
- + " restarting=" + entry.mRestarting);
-
- pw.print(prefix);
- pw.print(" imeToken=" + entry.mImeTokenString + " [" + entry.mImeId + "]");
- pw.print(" imeUserId=" + entry.mImeUserId);
- pw.println(" imeDisplayId=" + entry.mImeDisplayId);
-
- pw.print(prefix);
- pw.println(" targetWin=" + entry.mTargetWindowString
- + " [" + entry.mEditorInfo.packageName + "]"
- + " targetUserId=" + entry.mTargetUserId
- + " targetDisplayId=" + entry.mTargetDisplayId
- + " clientBindSeq=" + entry.mClientBindSequenceNumber);
-
- pw.print(prefix);
- pw.println(" softInputMode=" + InputMethodDebug.softInputModeToString(
- entry.mTargetWindowSoftInputMode));
-
- pw.print(prefix);
- pw.println(" inputType=0x" + Integer.toHexString(entry.mEditorInfo.inputType)
- + " imeOptions=0x" + Integer.toHexString(entry.mEditorInfo.imeOptions)
- + " fieldId=0x" + Integer.toHexString(entry.mEditorInfo.fieldId)
- + " fieldName=" + entry.mEditorInfo.fieldName
- + " actionId=" + entry.mEditorInfo.actionId
- + " actionLabel=" + entry.mEditorInfo.actionLabel);
- }
- }
- }
-
@GuardedBy("ImfLock.class")
@NonNull
private final StartInputHistory mStartInputHistory = new StartInputHistory();
@@ -1165,7 +871,18 @@
if (!mSystemReady) {
return;
}
- buildInputMethodListLocked(true);
+ for (int userId : mUserManagerInternal.getUserIds()) {
+ final InputMethodSettings settings = queryInputMethodServicesInternal(
+ mContext,
+ userId,
+ AdditionalSubtypeMapRepository.get(userId),
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, settings);
+ if (userId == mSettings.getUserId()) {
+ mSettings = settings;
+ }
+ }
+ postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */);
// If the locale is changed, needs to reset the default ime
resetDefaultImeLocked(mContext);
updateFromSettingsLocked(true);
@@ -1204,6 +921,14 @@
*/
private boolean mImePackageAppeared = false;
+ /**
+ * Remembers package names passed to {@link #onPackageDataCleared(String, int)}.
+ *
+ * <p>This field must be accessed only from callback methods in {@link PackageMonitor},
+ * which should be bound to {@link #getRegisteredHandler()}.</p>
+ */
+ private ArrayList<String> mDataClearedPackages = new ArrayList<>();
+
@GuardedBy("ImfLock.class")
void clearKnownImePackageNamesLocked() {
mKnownImePackageNames.clear();
@@ -1307,40 +1032,8 @@
@Override
public void onPackageDataCleared(String packageName, int uid) {
- final int userId = getChangingUserId();
- synchronized (ImfLock.class) {
- final boolean isCurrentUser = (userId == mSettings.getUserId());
- final AdditionalSubtypeMap additionalSubtypeMap;
- final InputMethodSettings settings;
- if (isCurrentUser) {
- additionalSubtypeMap = mAdditionalSubtypeMap;
- settings = mSettings;
- } else {
- additionalSubtypeMap = AdditionalSubtypeUtils.load(userId);
- settings = queryInputMethodServicesInternal(mContext, userId,
- additionalSubtypeMap, DirectBootAwareness.AUTO);
- }
-
- // Note that one package may implement multiple IMEs.
- final ArrayList<String> changedImes = new ArrayList<>();
- for (InputMethodInfo imi : settings.getMethodList()) {
- if (imi.getPackageName().equals(packageName)) {
- changedImes.add(imi.getId());
- }
- }
- final AdditionalSubtypeMap newMap =
- additionalSubtypeMap.cloneWithRemoveOrSelf(changedImes);
- if (newMap != additionalSubtypeMap) {
- if (isCurrentUser) {
- mAdditionalSubtypeMap = newMap;
- }
- AdditionalSubtypeUtils.save(
- newMap, settings.getMethodMap(), settings.getUserId());
- }
- if (!changedImes.isEmpty()) {
- mChangedPackages.add(packageName);
- }
- }
+ mChangedPackages.add(packageName);
+ mDataClearedPackages.add(packageName);
}
@Override
@@ -1352,6 +1045,7 @@
private void clearPackageChangeState() {
// No need to lock them because we access these fields only on getRegisteredHandler().
mChangedPackages.clear();
+ mDataClearedPackages.clear();
mImePackageAppeared = false;
}
@@ -1382,20 +1076,15 @@
synchronized (ImfLock.class) {
final int userId = getChangingUserId();
final boolean isCurrentUser = (userId == mSettings.getUserId());
- AdditionalSubtypeMap additionalSubtypeMap;
- final InputMethodSettings settings;
- if (isCurrentUser) {
- additionalSubtypeMap = mAdditionalSubtypeMap;
- settings = mSettings;
- } else {
- additionalSubtypeMap = AdditionalSubtypeUtils.load(userId);
- settings = queryInputMethodServicesInternal(mContext, userId,
- additionalSubtypeMap, DirectBootAwareness.AUTO);
- }
+ final AdditionalSubtypeMap additionalSubtypeMap =
+ AdditionalSubtypeMapRepository.get(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
InputMethodInfo curIm = null;
String curInputMethodId = settings.getSelectedInputMethod();
final List<InputMethodInfo> methodList = settings.getMethodList();
+
+ final ArrayList<String> imesToClearAdditionalSubtypes = new ArrayList<>();
final int numImes = methodList.size();
for (int i = 0; i < numImes; i++) {
InputMethodInfo imi = methodList.get(i);
@@ -1403,6 +1092,9 @@
if (imiId.equals(curInputMethodId)) {
curIm = imi;
}
+ if (mDataClearedPackages.contains(imi.getPackageName())) {
+ imesToClearAdditionalSubtypes.add(imiId);
+ }
int change = isPackageDisappearing(imi.getPackageName());
if (change == PACKAGE_TEMPORARY_CHANGE || change == PACKAGE_PERMANENT_CHANGE) {
Slog.i(TAG, "Input method uninstalled, disabling: " + imi.getComponent());
@@ -1417,21 +1109,33 @@
} else if (change == PACKAGE_UPDATING) {
Slog.i(TAG, "Input method reinstalling, clearing additional subtypes: "
+ imi.getComponent());
- additionalSubtypeMap =
- additionalSubtypeMap.cloneWithRemoveOrSelf(imi.getId());
- AdditionalSubtypeUtils.save(additionalSubtypeMap,
- settings.getMethodMap(), userId);
- if (isCurrentUser) {
- mAdditionalSubtypeMap = additionalSubtypeMap;
- }
+ imesToClearAdditionalSubtypes.add(imiId);
}
}
- if (!isCurrentUser || !shouldRebuildInputMethodListLocked()) {
+ // Clear additional subtypes as a batch operation.
+ final AdditionalSubtypeMap newAdditionalSubtypeMap =
+ additionalSubtypeMap.cloneWithRemoveOrSelf(imesToClearAdditionalSubtypes);
+ final boolean additionalSubtypeChanged =
+ (newAdditionalSubtypeMap != additionalSubtypeMap);
+ if (additionalSubtypeChanged) {
+ AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
+ settings.getMethodMap());
+ }
+ if (isCurrentUser
+ && !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) {
return;
}
- buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ userId, newAdditionalSubtypeMap, DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, newSettings);
+ if (!isCurrentUser) {
+ return;
+ }
+ mSettings = queryInputMethodServicesInternal(mContext, userId,
+ newAdditionalSubtypeMap, DirectBootAwareness.AUTO);
+ postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
boolean changed = false;
@@ -1515,6 +1219,7 @@
*/
@Nullable
@GuardedBy("ImfLock.class")
+ @MultiUserUnawareField
private UserSwitchHandlerTask mUserSwitchHandlerTask;
/**
@@ -1582,17 +1287,20 @@
void onUnlockUser(@UserIdInt int userId) {
synchronized (ImfLock.class) {
- final int currentUserId = mSettings.getUserId();
if (DEBUG) {
- Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + currentUserId);
+ Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId="
+ + mSettings.getUserId());
}
- if (userId != currentUserId) {
+ if (!mSystemReady) {
return;
}
- mSettings = InputMethodSettings.createEmptyMap(userId);
- if (mSystemReady) {
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, newSettings);
+ if (mSettings.getUserId() == userId) {
+ mSettings = newSettings;
// We need to rebuild IMEs.
- buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+ postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
}
}
@@ -1658,12 +1366,14 @@
mShowOngoingImeSwitcherForPhones = false;
+ // InputMethodSettingsRepository should be initialized before buildInputMethodListLocked
+ InputMethodSettingsRepository.initialize(mHandler, mContext);
+ AdditionalSubtypeMapRepository.initialize(mHandler, mContext);
+
final int userId = mActivityManagerInternal.getCurrentUserId();
- // mSettings should be created before buildInputMethodListLocked
- mSettings = InputMethodSettings.createEmptyMap(userId);
+ mSettings = InputMethodSettingsRepository.get(userId);
- mAdditionalSubtypeMap = AdditionalSubtypeUtils.load(userId);
mSwitchingController =
InputMethodSubtypeSwitchingController.createInstanceLocked(context,
mSettings.getMethodMap(), userId);
@@ -1808,8 +1518,6 @@
mSettingsObserver.registerContentObserverLocked(newUserId);
mSettings = InputMethodSettings.createEmptyMap(newUserId);
- // Additional subtypes should be reset when the user is changed
- mAdditionalSubtypeMap = AdditionalSubtypeUtils.load(newUserId);
final String defaultImiId = mSettings.getSelectedInputMethod();
if (DEBUG) {
@@ -1827,7 +1535,12 @@
// The mSystemReady flag is set during boot phase,
// and user switch would not happen at that time.
resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_USER);
- buildInputMethodListLocked(initialUserSwitch);
+
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ newUserId, AdditionalSubtypeMapRepository.get(newUserId), DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(newUserId, newSettings);
+ mSettings = newSettings;
+ postInputMethodSettingUpdatedLocked(initialUserSwitch /* resetDefaultEnabledIme */);
if (TextUtils.isEmpty(mSettings.getSelectedInputMethod())) {
// This is the first time of the user switch and
// set the current ime to the proper one.
@@ -1897,7 +1610,7 @@
}
}, "Lazily initialize IMMS#mImeDrawsImeNavBarRes");
- mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true);
+ mMyPackageMonitor.register(mContext, UserHandle.ALL, mHandler);
mSettingsObserver.registerContentObserverLocked(currentUserId);
final IntentFilter broadcastFilterForAllUsers = new IntentFilter();
@@ -1908,7 +1621,13 @@
final String defaultImiId = mSettings.getSelectedInputMethod();
final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
- buildInputMethodListLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */);
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ currentUserId, AdditionalSubtypeMapRepository.get(currentUserId),
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(currentUserId, newSettings);
+ mSettings = newSettings;
+ postInputMethodSettingUpdatedLocked(
+ !imeSelectedOnBoot /* resetDefaultEnabledIme */);
updateFromSettingsLocked(true);
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
getPackageManagerForUser(mContext, currentUserId),
@@ -2015,9 +1734,7 @@
&& (!connectionless
|| mBindingController.supportsConnectionlessStylusHandwriting());
}
- //TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList.
- //TODO(b/210039666): use cache.
- final InputMethodSettings settings = queryMethodMapForUser(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final InputMethodInfo imi = settings.getMethodMap().get(
settings.getSelectedInputMethod());
return imi != null && imi.supportsStylusHandwriting()
@@ -2041,11 +1758,11 @@
private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId,
@DirectBootAwareness int directBootAwareness, int callingUid) {
final InputMethodSettings settings;
- if (userId == mSettings.getUserId()
- && directBootAwareness == DirectBootAwareness.AUTO) {
- settings = mSettings;
+ if (directBootAwareness == DirectBootAwareness.AUTO) {
+ settings = InputMethodSettingsRepository.get(userId);
} else {
- final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeUtils.load(userId);
+ final AdditionalSubtypeMap additionalSubtypeMap =
+ AdditionalSubtypeMapRepository.get(userId);
settings = queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
directBootAwareness);
}
@@ -2066,7 +1783,7 @@
methodList = mSettings.getEnabledInputMethodList();
settings = mSettings;
} else {
- settings = queryMethodMapForUser(userId);
+ settings = InputMethodSettingsRepository.get(userId);
methodList = settings.getEnabledInputMethodList();
}
// filter caller's access to input methods
@@ -2126,22 +1843,7 @@
@GuardedBy("ImfLock.class")
private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId,
boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId, int callingUid) {
- if (userId == mSettings.getUserId()) {
- final InputMethodInfo imi;
- String selectedMethodId = getSelectedMethodIdLocked();
- if (imiId == null && selectedMethodId != null) {
- imi = mSettings.getMethodMap().get(selectedMethodId);
- } else {
- imi = mSettings.getMethodMap().get(imiId);
- }
- if (imi == null || !canCallerAccessInputMethod(
- imi.getPackageName(), callingUid, userId, mSettings)) {
- return Collections.emptyList();
- }
- return mSettings.getEnabledInputMethodSubtypeList(
- imi, allowsImplicitlyEnabledSubtypes);
- }
- final InputMethodSettings settings = queryMethodMapForUser(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final InputMethodInfo imi = settings.getMethodMap().get(imiId);
if (imi == null) {
return Collections.emptyList();
@@ -4307,8 +4009,7 @@
return mSettings.getLastInputMethodSubtype();
}
- final InputMethodSettings settings = queryMethodMapForUser(userId);
- return settings.getLastInputMethodSubtype();
+ return InputMethodSettingsRepository.get(userId).getLastInputMethodSubtype();
}
}
@@ -4338,33 +4039,27 @@
return;
}
- if (mSettings.getUserId() == userId) {
- final var newAdditionalSubtypeMap = mSettings.getNewAdditionalSubtypeMap(
- imiId, toBeAdded, mAdditionalSubtypeMap, mPackageManagerInternal,
- callingUid);
- if (mAdditionalSubtypeMap == newAdditionalSubtypeMap) {
- return;
- }
- AdditionalSubtypeUtils.save(newAdditionalSubtypeMap, mSettings.getMethodMap(),
- mSettings.getUserId());
- mAdditionalSubtypeMap = newAdditionalSubtypeMap;
- final long ident = Binder.clearCallingIdentity();
- try {
- buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- return;
- }
-
- final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeUtils.load(userId);
- final InputMethodSettings settings = queryInputMethodServicesInternal(mContext, userId,
- additionalSubtypeMap, DirectBootAwareness.AUTO);
+ final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
+ final boolean isCurrentUser = (mSettings.getUserId() == userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final var newAdditionalSubtypeMap = settings.getNewAdditionalSubtypeMap(
imiId, toBeAdded, additionalSubtypeMap, mPackageManagerInternal, callingUid);
if (additionalSubtypeMap != newAdditionalSubtypeMap) {
- AdditionalSubtypeUtils.save(newAdditionalSubtypeMap, settings.getMethodMap(),
- settings.getUserId());
+ AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
+ settings.getMethodMap());
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ userId, AdditionalSubtypeMapRepository.get(userId),
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, newSettings);
+ if (isCurrentUser) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mSettings = newSettings;
+ postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
}
}
}
@@ -4390,8 +4085,7 @@
try {
synchronized (ImfLock.class) {
final boolean currentUser = (mSettings.getUserId() == userId);
- final InputMethodSettings settings = currentUser
- ? mSettings : queryMethodMapForUser(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) {
return;
}
@@ -4995,6 +4689,14 @@
.getSortedInputMethodAndSubtypeList(
showAuxSubtypes, isScreenLocked, true /* forImeMenu */,
mContext, mSettings.getMethodMap(), mSettings.getUserId());
+ if (imList.isEmpty()) {
+ Slog.w(TAG, "Show switching menu failed, imList is empty,"
+ + " showAuxSubtypes: " + showAuxSubtypes
+ + " isScreenLocked: " + isScreenLocked
+ + " userId: " + mSettings.getUserId());
+ return false;
+ }
+
mMenuController.showInputMethodMenuLocked(showAuxSubtypes, displayId,
lastInputMethodId, lastInputMethodSubtypeId, imList);
}
@@ -5280,7 +4982,7 @@
}
@GuardedBy("ImfLock.class")
- void buildInputMethodListLocked(boolean resetDefaultEnabledIme) {
+ void postInputMethodSettingUpdatedLocked(boolean resetDefaultEnabledIme) {
if (DEBUG) {
Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
+ " \n ------ caller=" + Debug.getCallers(10));
@@ -5292,9 +4994,6 @@
mMethodMapUpdateCount++;
mMyPackageMonitor.clearKnownImePackageNamesLocked();
- mSettings = queryInputMethodServicesInternal(mContext, mSettings.getUserId(),
- mAdditionalSubtypeMap, DirectBootAwareness.AUTO);
-
// Construct the set of possible IME packages for onPackageChanged() to avoid false
// negatives when the package state remains to be the same but only the component state is
// changed.
@@ -5565,8 +5264,8 @@
return getCurrentInputMethodSubtypeLocked();
}
- final InputMethodSettings settings = queryMethodMapForUser(userId);
- return settings.getCurrentInputMethodSubtypeForNonCurrentUsers();
+ return InputMethodSettingsRepository.get(userId)
+ .getCurrentInputMethodSubtypeForNonCurrentUsers();
}
}
@@ -5628,23 +5327,10 @@
*/
@GuardedBy("ImfLock.class")
private InputMethodInfo queryDefaultInputMethodForUserIdLocked(@UserIdInt int userId) {
- final InputMethodSettings settings;
- if (userId == mSettings.getUserId()) {
- settings = mSettings;
- } else {
- final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeUtils.load(userId);
- settings = queryInputMethodServicesInternal(mContext, userId,
- additionalSubtypeMap, DirectBootAwareness.AUTO);
- }
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
return settings.getMethodMap().get(settings.getSelectedInputMethod());
}
- private InputMethodSettings queryMethodMapForUser(@UserIdInt int userId) {
- final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeUtils.load(userId);
- return queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
- DirectBootAwareness.AUTO);
- }
-
@GuardedBy("ImfLock.class")
private boolean switchToInputMethodLocked(String imeId, @UserIdInt int userId) {
if (userId == mSettings.getUserId()) {
@@ -5656,7 +5342,7 @@
setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
return true;
}
- final InputMethodSettings settings = queryMethodMapForUser(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
if (!settings.getMethodMap().containsKey(imeId)
|| !settings.getEnabledInputMethodList().contains(
settings.getMethodMap().get(imeId))) {
@@ -5796,7 +5482,7 @@
setInputMethodEnabledLocked(imeId, enabled);
return true;
}
- final InputMethodSettings settings = queryMethodMapForUser(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
if (!settings.getMethodMap().containsKey(imeId)) {
return false; // IME is not found.
}
@@ -6550,7 +6236,7 @@
previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled);
}
} else {
- final InputMethodSettings settings = queryMethodMapForUser(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
if (enabled) {
if (!settings.getMethodMap().containsKey(imeId)) {
failedToEnableUnknownIme = true;
@@ -6684,10 +6370,8 @@
nextIme = mSettings.getSelectedInputMethod();
nextEnabledImes = mSettings.getEnabledInputMethodList();
} else {
- final AdditionalSubtypeMap additionalSubtypeMap =
- AdditionalSubtypeUtils.load(userId);
- final InputMethodSettings settings = queryInputMethodServicesInternal(
- mContext, userId, additionalSubtypeMap, DirectBootAwareness.AUTO);
+ final InputMethodSettings settings =
+ InputMethodSettingsRepository.get(userId);
nextEnabledImes = InputMethodInfoUtils.getDefaultEnabledImes(mContext,
settings.getMethodList());
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index 6ed4848..3bd0a9f 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -80,10 +80,6 @@
final int userId = mService.getCurrentImeUserIdLocked();
- if (imList.isEmpty()) {
- return;
- }
-
hideInputMethodMenuLocked();
if (preferredInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
new file mode 100644
index 0000000..60b9a4c
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.Handler;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+final class InputMethodSettingsRepository {
+ @GuardedBy("ImfLock.class")
+ @NonNull
+ private static final SparseArray<InputMethodSettings> sPerUserMap = new SparseArray<>();
+
+ /**
+ * Not intended to be instantiated.
+ */
+ private InputMethodSettingsRepository() {
+ }
+
+ @NonNull
+ @GuardedBy("ImfLock.class")
+ static InputMethodSettings get(@UserIdInt int userId) {
+ final InputMethodSettings obj = sPerUserMap.get(userId);
+ if (obj != null) {
+ return obj;
+ }
+ return InputMethodSettings.createEmptyMap(userId);
+ }
+
+ @GuardedBy("ImfLock.class")
+ static void put(@UserIdInt int userId, @NonNull InputMethodSettings obj) {
+ sPerUserMap.put(userId, obj);
+ }
+
+ static void initialize(@NonNull Handler handler, @NonNull Context context) {
+ final UserManagerInternal userManagerInternal =
+ LocalServices.getService(UserManagerInternal.class);
+ handler.post(() -> {
+ userManagerInternal.addUserLifecycleListener(
+ new UserManagerInternal.UserLifecycleListener() {
+ @Override
+ public void onUserRemoved(UserInfo user) {
+ final int userId = user.id;
+ handler.post(() -> {
+ synchronized (ImfLock.class) {
+ sPerUserMap.remove(userId);
+ }
+ });
+ }
+ });
+ synchronized (ImfLock.class) {
+ for (int userId : userManagerInternal.getUserIds()) {
+ final InputMethodSettings settings =
+ InputMethodManagerService.queryInputMethodServicesInternal(
+ context,
+ userId,
+ AdditionalSubtypeMapRepository.get(userId),
+ DirectBootAwareness.AUTO);
+ sPerUserMap.put(userId, settings);
+ }
+ }
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index 1379d16..1c958a9 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -167,6 +167,7 @@
final ArrayList<InputMethodInfo> imis = settings.getEnabledInputMethodList();
if (imis.isEmpty()) {
+ Slog.w(TAG, "Enabled input method list is empty.");
return new ArrayList<>();
}
if (isScreenLocked && includeAuxiliarySubtypes) {
diff --git a/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java b/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java
new file mode 100644
index 0000000..3023603
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.SystemClock;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+
+import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicInteger;
+
+final class SoftInputShowHideHistory {
+ private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
+
+ private final Entry[] mEntries = new Entry[16];
+ private int mNextIndex = 0;
+
+ static final class Entry {
+ final int mSequenceNumber = sSequenceNumber.getAndIncrement();
+ @Nullable
+ final ClientState mClientState;
+ @WindowManager.LayoutParams.SoftInputModeFlags
+ final int mFocusedWindowSoftInputMode;
+ @SoftInputShowHideReason
+ final int mReason;
+ // The timing of handling showCurrentInputLocked() or hideCurrentInputLocked().
+ final long mTimestamp;
+ final long mWallTime;
+ final boolean mInFullscreenMode;
+ @NonNull
+ final String mFocusedWindowName;
+ @Nullable
+ final EditorInfo mEditorInfo;
+ @NonNull
+ final String mRequestWindowName;
+ @Nullable
+ final String mImeControlTargetName;
+ @Nullable
+ final String mImeTargetNameFromWm;
+ @Nullable
+ final String mImeSurfaceParentName;
+
+ Entry(ClientState client, EditorInfo editorInfo,
+ String focusedWindowName,
+ @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode,
+ @SoftInputShowHideReason int reason,
+ boolean inFullscreenMode, String requestWindowName,
+ @Nullable String imeControlTargetName, @Nullable String imeTargetName,
+ @Nullable String imeSurfaceParentName) {
+ mClientState = client;
+ mEditorInfo = editorInfo;
+ mFocusedWindowName = focusedWindowName;
+ mFocusedWindowSoftInputMode = softInputMode;
+ mReason = reason;
+ mTimestamp = SystemClock.uptimeMillis();
+ mWallTime = System.currentTimeMillis();
+ mInFullscreenMode = inFullscreenMode;
+ mRequestWindowName = requestWindowName;
+ mImeControlTargetName = imeControlTargetName;
+ mImeTargetNameFromWm = imeTargetName;
+ mImeSurfaceParentName = imeSurfaceParentName;
+ }
+ }
+
+ void addEntry(@NonNull Entry entry) {
+ final int index = mNextIndex;
+ mEntries[index] = entry;
+ mNextIndex = (mNextIndex + 1) % mEntries.length;
+ }
+
+ void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ final DateTimeFormatter formatter =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+ .withZone(ZoneId.systemDefault());
+
+ for (int i = 0; i < mEntries.length; ++i) {
+ final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
+ if (entry == null) {
+ continue;
+ }
+ pw.print(prefix);
+ pw.println("SoftInputShowHide #" + entry.mSequenceNumber + ":");
+
+ pw.print(prefix);
+ pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
+ + " (timestamp=" + entry.mTimestamp + ")");
+
+ pw.print(prefix);
+ pw.print(" reason=" + InputMethodDebug.softInputDisplayReasonToString(
+ entry.mReason));
+ pw.println(" inFullscreenMode=" + entry.mInFullscreenMode);
+
+ pw.print(prefix);
+ pw.println(" requestClient=" + entry.mClientState);
+
+ pw.print(prefix);
+ pw.println(" focusedWindowName=" + entry.mFocusedWindowName);
+
+ pw.print(prefix);
+ pw.println(" requestWindowName=" + entry.mRequestWindowName);
+
+ pw.print(prefix);
+ pw.println(" imeControlTargetName=" + entry.mImeControlTargetName);
+
+ pw.print(prefix);
+ pw.println(" imeTargetNameFromWm=" + entry.mImeTargetNameFromWm);
+
+ pw.print(prefix);
+ pw.println(" imeSurfaceParentName=" + entry.mImeSurfaceParentName);
+
+ pw.print(prefix);
+ pw.print(" editorInfo:");
+ if (entry.mEditorInfo != null) {
+ pw.print(" inputType=" + entry.mEditorInfo.inputType);
+ pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
+ pw.println(" fieldId (viewId)=" + entry.mEditorInfo.fieldId);
+ } else {
+ pw.println(" null");
+ }
+
+ pw.print(prefix);
+ pw.println(" focusedWindowSoftInputMode=" + InputMethodDebug.softInputModeToString(
+ entry.mFocusedWindowSoftInputMode));
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/StartInputHistory.java b/services/core/java/com/android/server/inputmethod/StartInputHistory.java
new file mode 100644
index 0000000..3a39434
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/StartInputHistory.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.StartInputReason;
+
+import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+/**
+ * A ring buffer to store the history of {@link StartInputInfo}.
+ */
+final class StartInputHistory {
+ /**
+ * Entry size for non low-RAM devices.
+ *
+ * <p>TODO: Consider to follow what other system services have been doing to manage
+ * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
+ */
+ private static final int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 32;
+
+ /**
+ * Entry size for low-RAM devices.
+ *
+ * <p>TODO: Consider to follow what other system services have been doing to manage
+ * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
+ */
+ private static final int ENTRY_SIZE_FOR_LOW_RAM_DEVICE = 5;
+
+ private static int getEntrySize() {
+ if (ActivityManager.isLowRamDeviceStatic()) {
+ return ENTRY_SIZE_FOR_LOW_RAM_DEVICE;
+ } else {
+ return ENTRY_SIZE_FOR_HIGH_RAM_DEVICE;
+ }
+ }
+
+ /**
+ * Backing store for the ring buffer.
+ */
+ private final Entry[] mEntries = new Entry[getEntrySize()];
+
+ /**
+ * An index of {@link #mEntries}, to which next
+ * {@link #addEntry(StartInputInfo)} should
+ * write.
+ */
+ private int mNextIndex = 0;
+
+ /**
+ * Recyclable entry to store the information in {@link StartInputInfo}.
+ */
+ private static final class Entry {
+ int mSequenceNumber;
+ long mTimestamp;
+ long mWallTime;
+ @UserIdInt
+ int mImeUserId;
+ @NonNull
+ String mImeTokenString;
+ int mImeDisplayId;
+ @NonNull
+ String mImeId;
+ @StartInputReason
+ int mStartInputReason;
+ boolean mRestarting;
+ @UserIdInt
+ int mTargetUserId;
+ int mTargetDisplayId;
+ @NonNull
+ String mTargetWindowString;
+ @NonNull
+ EditorInfo mEditorInfo;
+ @WindowManager.LayoutParams.SoftInputModeFlags
+ int mTargetWindowSoftInputMode;
+ int mClientBindSequenceNumber;
+
+ Entry(@NonNull StartInputInfo original) {
+ set(original);
+ }
+
+ void set(@NonNull StartInputInfo original) {
+ mSequenceNumber = original.mSequenceNumber;
+ mTimestamp = original.mTimestamp;
+ mWallTime = original.mWallTime;
+ mImeUserId = original.mImeUserId;
+ // Intentionally convert to String so as not to keep a strong reference to a Binder
+ // object.
+ mImeTokenString = String.valueOf(original.mImeToken);
+ mImeDisplayId = original.mImeDisplayId;
+ mImeId = original.mImeId;
+ mStartInputReason = original.mStartInputReason;
+ mRestarting = original.mRestarting;
+ mTargetUserId = original.mTargetUserId;
+ mTargetDisplayId = original.mTargetDisplayId;
+ // Intentionally convert to String so as not to keep a strong reference to a Binder
+ // object.
+ mTargetWindowString = String.valueOf(original.mTargetWindow);
+ mEditorInfo = original.mEditorInfo;
+ mTargetWindowSoftInputMode = original.mTargetWindowSoftInputMode;
+ mClientBindSequenceNumber = original.mClientBindSequenceNumber;
+ }
+ }
+
+ /**
+ * Add a new entry and discard the oldest entry as needed.
+ *
+ * @param info {@link StartInputInfo} to be added.
+ */
+ void addEntry(@NonNull StartInputInfo info) {
+ final int index = mNextIndex;
+ if (mEntries[index] == null) {
+ mEntries[index] = new Entry(info);
+ } else {
+ mEntries[index].set(info);
+ }
+ mNextIndex = (mNextIndex + 1) % mEntries.length;
+ }
+
+ void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ final DateTimeFormatter formatter =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+ .withZone(ZoneId.systemDefault());
+
+ for (int i = 0; i < mEntries.length; ++i) {
+ final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
+ if (entry == null) {
+ continue;
+ }
+ pw.print(prefix);
+ pw.println("StartInput #" + entry.mSequenceNumber + ":");
+
+ pw.print(prefix);
+ pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
+ + " (timestamp=" + entry.mTimestamp + ")"
+ + " reason="
+ + InputMethodDebug.startInputReasonToString(entry.mStartInputReason)
+ + " restarting=" + entry.mRestarting);
+
+ pw.print(prefix);
+ pw.print(" imeToken=" + entry.mImeTokenString + " [" + entry.mImeId + "]");
+ pw.print(" imeUserId=" + entry.mImeUserId);
+ pw.println(" imeDisplayId=" + entry.mImeDisplayId);
+
+ pw.print(prefix);
+ pw.println(" targetWin=" + entry.mTargetWindowString
+ + " [" + entry.mEditorInfo.packageName + "]"
+ + " targetUserId=" + entry.mTargetUserId
+ + " targetDisplayId=" + entry.mTargetDisplayId
+ + " clientBindSeq=" + entry.mClientBindSequenceNumber);
+
+ pw.print(prefix);
+ pw.println(" softInputMode=" + InputMethodDebug.softInputModeToString(
+ entry.mTargetWindowSoftInputMode));
+
+ pw.print(prefix);
+ pw.println(" inputType=0x" + Integer.toHexString(entry.mEditorInfo.inputType)
+ + " imeOptions=0x" + Integer.toHexString(entry.mEditorInfo.imeOptions)
+ + " fieldId=0x" + Integer.toHexString(entry.mEditorInfo.fieldId)
+ + " fieldName=" + entry.mEditorInfo.fieldName
+ + " actionId=" + entry.mEditorInfo.actionId
+ + " actionLabel=" + entry.mEditorInfo.actionLabel);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/StartInputInfo.java b/services/core/java/com/android/server/inputmethod/StartInputInfo.java
new file mode 100644
index 0000000..1cff737
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/StartInputInfo.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.inputmethodservice.InputMethodService;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+
+import com.android.internal.inputmethod.IInputMethod;
+import com.android.internal.inputmethod.StartInputReason;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Internal state snapshot when
+ * {@link IInputMethod#startInput(IInputMethod.StartInputParams)} is about to be called.
+ *
+ * <p>Calling that IPC endpoint basically means that
+ * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
+ * back in the current IME process shortly, which will also affect what the current IME starts
+ * receiving from {@link InputMethodService#getCurrentInputConnection()}. In other words, this
+ * snapshot will be taken every time when {@link InputMethodManagerService} is initiating a new
+ * logical input session between the client application and the current IME.</p>
+ *
+ * <p>Be careful to not keep strong references to this object forever, which can prevent
+ * {@link StartInputInfo#mImeToken} and {@link StartInputInfo#mTargetWindow} from being GC-ed.
+ * </p>
+ */
+final class StartInputInfo {
+ private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
+
+ final int mSequenceNumber;
+ final long mTimestamp;
+ final long mWallTime;
+ @UserIdInt
+ final int mImeUserId;
+ @NonNull
+ final IBinder mImeToken;
+ final int mImeDisplayId;
+ @NonNull
+ final String mImeId;
+ @StartInputReason
+ final int mStartInputReason;
+ final boolean mRestarting;
+ @UserIdInt
+ final int mTargetUserId;
+ final int mTargetDisplayId;
+ @Nullable
+ final IBinder mTargetWindow;
+ @NonNull
+ final EditorInfo mEditorInfo;
+ @WindowManager.LayoutParams.SoftInputModeFlags
+ final int mTargetWindowSoftInputMode;
+ final int mClientBindSequenceNumber;
+
+ StartInputInfo(@UserIdInt int imeUserId, @NonNull IBinder imeToken, int imeDisplayId,
+ @NonNull String imeId, @StartInputReason int startInputReason, boolean restarting,
+ @UserIdInt int targetUserId, int targetDisplayId, @Nullable IBinder targetWindow,
+ @NonNull EditorInfo editorInfo,
+ @WindowManager.LayoutParams.SoftInputModeFlags int targetWindowSoftInputMode,
+ int clientBindSequenceNumber) {
+ mSequenceNumber = sSequenceNumber.getAndIncrement();
+ mTimestamp = SystemClock.uptimeMillis();
+ mWallTime = System.currentTimeMillis();
+ mImeUserId = imeUserId;
+ mImeToken = imeToken;
+ mImeDisplayId = imeDisplayId;
+ mImeId = imeId;
+ mStartInputReason = startInputReason;
+ mRestarting = restarting;
+ mTargetUserId = targetUserId;
+ mTargetDisplayId = targetDisplayId;
+ mTargetWindow = targetWindow;
+ mEditorInfo = editorInfo;
+ mTargetWindowSoftInputMode = targetWindowSoftInputMode;
+ mClientBindSequenceNumber = clientBindSequenceNumber;
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
index 9caf5cf..396192e 100644
--- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
+++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
@@ -101,7 +101,6 @@
}
private void offloadInner(Runnable r) {
- boolean useThrowingRunnable = r instanceof ThrowingRunnable;
final long identity = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> {
@@ -110,14 +109,9 @@
Binder.restoreCallingIdentity(identity);
try {
try {
- if (useThrowingRunnable) {
- ((ThrowingRunnable) r).runOrThrow();
- } else {
- r.run();
- }
+ r.run();
} catch (Exception e) {
- Slog.e(TAG, "Error in async call", e);
- throw ExceptionUtils.propagate(e);
+ Slog.e(TAG, "Error in async IMMS call", e);
}
} finally {
Binder.restoreCallingIdentity(inner);
diff --git a/services/core/java/com/android/server/location/contexthub/OWNERS b/services/core/java/com/android/server/location/contexthub/OWNERS
index 90c2330..c62e323 100644
--- a/services/core/java/com/android/server/location/contexthub/OWNERS
+++ b/services/core/java/com/android/server/location/contexthub/OWNERS
@@ -1,3 +1,3 @@
-arthuri@google.com
bduddie@google.com
+matthewsedam@google.com
stange@google.com
diff --git a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
index f60f55c..7acc3ef 100644
--- a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
+++ b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
@@ -374,6 +374,7 @@
event.getInputMediaItemInfos().isEmpty()
? EMPTY_MEDIA_ITEM_INFO
: event.getInputMediaItemInfos().get(0);
+ @MediaItemInfo.DataType long inputDataTypes = inputMediaItemInfo.getDataTypes();
String inputAudioSampleMimeType =
getFilteredFirstMimeType(
inputMediaItemInfo.getSampleMimeTypes(), AUDIO_MIME_TYPE_PREFIX);
@@ -396,6 +397,7 @@
event.getOutputMediaItemInfo() == null
? EMPTY_MEDIA_ITEM_INFO
: event.getOutputMediaItemInfo();
+ @MediaItemInfo.DataType long outputDataTypes = outputMediaItemInfo.getDataTypes();
String outputAudioSampleMimeType =
getFilteredFirstMimeType(
outputMediaItemInfo.getSampleMimeTypes(), AUDIO_MIME_TYPE_PREFIX);
@@ -415,6 +417,7 @@
!outputCodecNames.isEmpty() ? outputCodecNames.get(0) : "";
String outputSecondCodecName =
outputCodecNames.size() > 1 ? outputCodecNames.get(1) : "";
+ @EditingEndedEvent.OperationType long operationTypes = event.getOperationTypes();
StatsEvent statsEvent =
StatsEvent.newBuilder()
.setAtomId(798)
@@ -423,11 +426,63 @@
.writeFloat(event.getFinalProgressPercent())
.writeInt(event.getErrorCode())
.writeLong(event.getTimeSinceCreatedMillis())
+ .writeBoolean(
+ (operationTypes
+ & EditingEndedEvent
+ .OPERATION_TYPE_VIDEO_TRANSCODE)
+ != 0)
+ .writeBoolean(
+ (operationTypes
+ & EditingEndedEvent
+ .OPERATION_TYPE_AUDIO_TRANSCODE)
+ != 0)
+ .writeBoolean(
+ (operationTypes & EditingEndedEvent.OPERATION_TYPE_VIDEO_EDIT)
+ != 0)
+ .writeBoolean(
+ (operationTypes & EditingEndedEvent.OPERATION_TYPE_AUDIO_EDIT)
+ != 0)
+ .writeBoolean(
+ (operationTypes
+ & EditingEndedEvent
+ .OPERATION_TYPE_VIDEO_TRANSMUX)
+ != 0)
+ .writeBoolean(
+ (operationTypes
+ & EditingEndedEvent
+ .OPERATION_TYPE_AUDIO_TRANSMUX)
+ != 0)
+ .writeBoolean(
+ (operationTypes & EditingEndedEvent.OPERATION_TYPE_PAUSED) != 0)
+ .writeBoolean(
+ (operationTypes & EditingEndedEvent.OPERATION_TYPE_RESUMED)
+ != 0)
.writeString(getFilteredLibraryName(event.getExporterName()))
.writeString(getFilteredLibraryName(event.getMuxerName()))
.writeInt(getThroughputFps(event))
.writeInt(event.getInputMediaItemInfos().size())
.writeInt(inputMediaItemInfo.getSourceType())
+ .writeBoolean((inputDataTypes & MediaItemInfo.DATA_TYPE_IMAGE) != 0)
+ .writeBoolean((inputDataTypes & MediaItemInfo.DATA_TYPE_VIDEO) != 0)
+ .writeBoolean((inputDataTypes & MediaItemInfo.DATA_TYPE_AUDIO) != 0)
+ .writeBoolean((inputDataTypes & MediaItemInfo.DATA_TYPE_METADATA) != 0)
+ .writeBoolean((inputDataTypes & MediaItemInfo.DATA_TYPE_DEPTH) != 0)
+ .writeBoolean((inputDataTypes & MediaItemInfo.DATA_TYPE_GAIN_MAP) != 0)
+ .writeBoolean(
+ (inputDataTypes & MediaItemInfo.DATA_TYPE_HIGH_FRAME_RATE) != 0)
+ .writeBoolean(
+ (inputDataTypes
+ & MediaItemInfo
+ .DATA_TYPE_SPEED_SETTING_CUE_POINTS)
+ != 0)
+ .writeBoolean((inputDataTypes & MediaItemInfo.DATA_TYPE_GAPLESS) != 0)
+ .writeBoolean(
+ (inputDataTypes & MediaItemInfo.DATA_TYPE_SPATIAL_AUDIO) != 0)
+ .writeBoolean(
+ (inputDataTypes
+ & MediaItemInfo
+ .DATA_TYPE_HIGH_DYNAMIC_RANGE_VIDEO)
+ != 0)
.writeLong(
getBucketedDurationMillis(
inputMediaItemInfo.getDurationMillis()))
@@ -443,6 +498,7 @@
getFilteredAudioSampleRateHz(
inputMediaItemInfo.getAudioSampleRateHz()))
.writeInt(inputMediaItemInfo.getAudioChannelCount())
+ .writeLong(inputMediaItemInfo.getAudioSampleCount())
.writeInt(inputVideoSize.getWidth())
.writeInt(inputVideoSize.getHeight())
.writeInt(inputVideoResolution)
@@ -456,6 +512,28 @@
.writeInt(getVideoFrameRateEnum(inputMediaItemInfo.getVideoFrameRate()))
.writeString(inputFirstCodecName)
.writeString(inputSecondCodecName)
+ .writeBoolean((outputDataTypes & MediaItemInfo.DATA_TYPE_IMAGE) != 0)
+ .writeBoolean((outputDataTypes & MediaItemInfo.DATA_TYPE_VIDEO) != 0)
+ .writeBoolean((outputDataTypes & MediaItemInfo.DATA_TYPE_AUDIO) != 0)
+ .writeBoolean((outputDataTypes & MediaItemInfo.DATA_TYPE_METADATA) != 0)
+ .writeBoolean((outputDataTypes & MediaItemInfo.DATA_TYPE_DEPTH) != 0)
+ .writeBoolean((outputDataTypes & MediaItemInfo.DATA_TYPE_GAIN_MAP) != 0)
+ .writeBoolean(
+ (outputDataTypes & MediaItemInfo.DATA_TYPE_HIGH_FRAME_RATE)
+ != 0)
+ .writeBoolean(
+ (outputDataTypes
+ & MediaItemInfo
+ .DATA_TYPE_SPEED_SETTING_CUE_POINTS)
+ != 0)
+ .writeBoolean((outputDataTypes & MediaItemInfo.DATA_TYPE_GAPLESS) != 0)
+ .writeBoolean(
+ (outputDataTypes & MediaItemInfo.DATA_TYPE_SPATIAL_AUDIO) != 0)
+ .writeBoolean(
+ (outputDataTypes
+ & MediaItemInfo
+ .DATA_TYPE_HIGH_DYNAMIC_RANGE_VIDEO)
+ != 0)
.writeLong(
getBucketedDurationMillis(
outputMediaItemInfo.getDurationMillis()))
@@ -471,6 +549,7 @@
getFilteredAudioSampleRateHz(
outputMediaItemInfo.getAudioSampleRateHz()))
.writeInt(outputMediaItemInfo.getAudioChannelCount())
+ .writeLong(outputMediaItemInfo.getAudioSampleCount())
.writeInt(outputVideoSize.getWidth())
.writeInt(outputVideoSize.getHeight())
.writeInt(outputVideoResolution)
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 18b495b..97ce77c 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -340,6 +340,8 @@
static final String TAG = NetworkPolicyLogger.TAG;
private static final boolean LOGD = NetworkPolicyLogger.LOGD;
private static final boolean LOGV = NetworkPolicyLogger.LOGV;
+ // TODO: b/304347838 - Remove once the feature is in staging.
+ private static final boolean ALWAYS_RESTRICT_BACKGROUND_NETWORK = false;
/**
* No opportunistic quota could be calculated from user data plan or data settings.
@@ -1068,7 +1070,8 @@
}
// The flag is boot-stable.
- mBackgroundNetworkRestricted = Flags.networkBlockedForTopSleepingAndAbove();
+ mBackgroundNetworkRestricted = ALWAYS_RESTRICT_BACKGROUND_NETWORK
+ && Flags.networkBlockedForTopSleepingAndAbove();
if (mBackgroundNetworkRestricted) {
// Firewall rules and UidBlockedState will get updated in
// updateRulesForGlobalChangeAL below.
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b98424c..c1a4571e 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -338,6 +338,7 @@
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.TriPredicate;
@@ -1825,6 +1826,12 @@
}
}
+ protected void logSensitiveAdjustmentReceived(boolean hasPosted,
+ boolean hasSensitiveContent, int lifespanMs) {
+ FrameworkStatsLog.write(FrameworkStatsLog.SENSITIVE_NOTIFICATION_REDACTION, hasPosted,
+ hasSensitiveContent, lifespanMs);
+ }
+
@GuardedBy("mNotificationLock")
void clearSoundLocked() {
mSoundNotificationKey = null;
@@ -6384,7 +6391,7 @@
if (Objects.equals(adjustment.getKey(), r.getKey())
&& Objects.equals(adjustment.getUser(), r.getUserId())
&& mAssistants.isSameUser(token, r.getUserId())) {
- applyAdjustment(r, adjustment);
+ applyAdjustmentLocked(r, adjustment, false);
r.applyAdjustments();
// importance is checked at the beginning of the
// PostNotificationRunnable, before the signal extractors are run, so
@@ -6394,7 +6401,7 @@
}
}
if (!foundEnqueued) {
- applyAdjustmentFromAssistant(token, adjustment);
+ applyAdjustmentsFromAssistant(token, List.of(adjustment));
}
}
} finally {
@@ -6422,7 +6429,7 @@
for (Adjustment adjustment : adjustments) {
NotificationRecord r = mNotificationsByKey.get(adjustment.getKey());
if (r != null && mAssistants.isSameUser(token, r.getUserId())) {
- applyAdjustment(r, adjustment);
+ applyAdjustmentLocked(r, adjustment, true);
// If the assistant has blocked the notification, cancel it
// This will trigger a sort, so we don't have to explicitly ask for
// one here.
@@ -6706,7 +6713,9 @@
}
}
- private void applyAdjustment(NotificationRecord r, Adjustment adjustment) {
+ @GuardedBy("mNotificationLock")
+ private void applyAdjustmentLocked(NotificationRecord r, Adjustment adjustment,
+ boolean isPosted) {
if (r == null) {
return;
}
@@ -6723,6 +6732,11 @@
adjustments.remove(removeKey);
}
r.addAdjustment(adjustment);
+ if (adjustment.getSignals().containsKey(Adjustment.KEY_SENSITIVE_CONTENT)) {
+ logSensitiveAdjustmentReceived(isPosted,
+ adjustment.getSignals().getBoolean(Adjustment.KEY_SENSITIVE_CONTENT),
+ r.getLifespanMs(System.currentTimeMillis()));
+ }
}
}
@@ -8210,7 +8224,7 @@
try {
return mTelecomManager.isInManagedCall()
|| mTelecomManager.isInSelfManagedCall(pkg,
- /* hasCrossUserAccess */ true);
+ UserHandle.ALL);
} catch (IllegalStateException ise) {
// Telecom is not ready (this is likely early boot), so there are no calls.
return false;
@@ -12054,10 +12068,17 @@
@Override
public void onServiceAdded(ManagedServiceInfo info) {
if (lifetimeExtensionRefactor()) {
- // We explicitly check the status bar permission for the uid in the info object.
- // We can't use the calling uid here because it's probably always system server.
- // Note that this will also be true for the shell.
- info.isSystemUi = getContext().checkPermission(
+ // Generally, only System or System UI should have the permissions to call
+ // registerSystemService.
+ // isCallerSystemOrPhone tells us whether the caller is System. We negate this,
+ // to eliminate cases where the service was added by the system. This leaves
+ // services registered by system server.
+ // To identify system UI, we explicitly check the status bar permission for the
+ // uid in the info object.
+ // We can't use the calling uid here because it belongs to system server.
+ // Note that this will also return true for the shell, but we deem this
+ // acceptable, for the purposes of testing.
+ info.isSystemUi = !isCallerSystemOrPhone() && getContext().checkPermission(
android.Manifest.permission.STATUS_BAR_SERVICE, -1, info.uid)
== PERMISSION_GRANTED;
}
diff --git a/core/java/android/app/ondeviceintelligence/Content.aidl b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java
similarity index 72%
rename from core/java/android/app/ondeviceintelligence/Content.aidl
rename to services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java
index 40f0ef9..81f11b5 100644
--- a/core/java/android/app/ondeviceintelligence/Content.aidl
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java
@@ -1,5 +1,5 @@
-/**
- * Copyright (c) 2024, The Android Open Source Project
+/*
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package android.app.ondeviceintelligence;
+package com.android.server.ondeviceintelligence;
-/**
- * @hide
- */
-parcelable Content;
+public interface OnDeviceIntelligenceManagerInternal {
+ String getRemoteServicePackageName();
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
index 71800ef..28682e3 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
@@ -16,12 +16,11 @@
package com.android.server.ondeviceintelligence;
-import static android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException.PROCESSING_UPDATE_STATUS_CONNECTION_FAILED;
-
import android.Manifest;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.app.AppGlobals;
-import android.app.ondeviceintelligence.Content;
import android.app.ondeviceintelligence.DownloadCallback;
import android.app.ondeviceintelligence.Feature;
import android.app.ondeviceintelligence.IDownloadCallback;
@@ -33,25 +32,32 @@
import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.IStreamingResponseCallback;
import android.app.ondeviceintelligence.ITokenInfoCallback;
-import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.os.Binder;
+import android.content.res.Resources;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
import android.os.ICancellationSignal;
+import android.os.Looper;
+import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.service.ondeviceintelligence.IOnDeviceIntelligenceService;
import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService;
+import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
import android.service.ondeviceintelligence.IRemoteProcessingService;
import android.service.ondeviceintelligence.IRemoteStorageService;
-import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
import android.service.ondeviceintelligence.OnDeviceIntelligenceService;
import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
import android.text.TextUtils;
@@ -62,8 +68,10 @@
import com.android.internal.infra.AndroidFuture;
import com.android.internal.infra.ServiceConnector;
import com.android.internal.os.BackgroundThread;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
+import java.io.FileDescriptor;
import java.util.Objects;
import java.util.Set;
@@ -84,6 +92,9 @@
private static final String TAG = OnDeviceIntelligenceManagerService.class.getSimpleName();
private static final String KEY_SERVICE_ENABLED = "service_enabled";
+ /** Handler message to {@link #resetTemporaryServices()} */
+ private static final int MSG_RESET_TEMPORARY_SERVICE = 0;
+
/** Default value in absence of {@link DeviceConfig} override. */
private static final boolean DEFAULT_SERVICE_ENABLED = true;
private static final String NAMESPACE_ON_DEVICE_INTELLIGENCE = "ondeviceintelligence";
@@ -96,19 +107,30 @@
private RemoteOnDeviceIntelligenceService mRemoteOnDeviceIntelligenceService;
volatile boolean mIsServiceEnabled;
+ @GuardedBy("mLock")
+ private String[] mTemporaryServiceNames;
+
+ /**
+ * Handler used to reset the temporary service names.
+ */
+ @GuardedBy("mLock")
+ private Handler mTemporaryHandler;
+
public OnDeviceIntelligenceManagerService(Context context) {
super(context);
mContext = context;
+ mTemporaryServiceNames = new String[0];
}
@Override
public void onStart() {
publishBinderService(
- Context.ON_DEVICE_INTELLIGENCE_SERVICE, new OnDeviceIntelligenceManagerInternal(),
+ Context.ON_DEVICE_INTELLIGENCE_SERVICE, getOnDeviceIntelligenceManagerService(),
/* allowIsolated = */true);
+ LocalServices.addService(OnDeviceIntelligenceManagerInternal.class,
+ OnDeviceIntelligenceManagerService.this::getRemoteConfiguredPackageName);
}
-
@Override
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
@@ -133,195 +155,211 @@
KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
}
- private final class OnDeviceIntelligenceManagerInternal extends
- IOnDeviceIntelligenceManager.Stub {
- @Override
- public void getVersion(RemoteCallback remoteCallback) throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getVersion");
- Objects.requireNonNull(remoteCallback);
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- remoteCallback.sendResult(null);
- return;
+ private IBinder getOnDeviceIntelligenceManagerService() {
+ return new IOnDeviceIntelligenceManager.Stub() {
+ @Override
+ public String getRemoteServicePackageName() {
+ return OnDeviceIntelligenceManagerService.this.getRemoteConfiguredPackageName();
}
- ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.post(
- service -> service.getVersion(remoteCallback));
- }
- @Override
- public void getFeature(int id, IFeatureCallback featureCallback) throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
- Objects.requireNonNull(featureCallback);
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- featureCallback.onFailure(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- new PersistableBundle());
- return;
+ @Override
+ public void getVersion(RemoteCallback remoteCallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getVersion");
+ Objects.requireNonNull(remoteCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ remoteCallback.sendResult(null);
+ return;
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.run(
+ service -> service.getVersion(remoteCallback));
}
- ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.post(
- service -> service.getFeature(Binder.getCallingUid(), id, featureCallback));
- }
- @Override
- public void listFeatures(IListFeaturesCallback listFeaturesCallback)
- throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
- Objects.requireNonNull(listFeaturesCallback);
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- listFeaturesCallback.onFailure(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- new PersistableBundle());
- return;
+ @Override
+ public void getFeature(int id, IFeatureCallback featureCallback)
+ throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
+ Objects.requireNonNull(featureCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ featureCallback.onFailure(
+ OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ return;
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.run(
+ service -> service.getFeature(Binder.getCallingUid(), id, featureCallback));
}
- ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.post(
- service -> service.listFeatures(Binder.getCallingUid(), listFeaturesCallback));
- }
- @Override
- public void getFeatureDetails(Feature feature,
- IFeatureDetailsCallback featureDetailsCallback)
- throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatureStatus");
- Objects.requireNonNull(feature);
- Objects.requireNonNull(featureDetailsCallback);
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- featureDetailsCallback.onFailure(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- new PersistableBundle());
- return;
+ @Override
+ public void listFeatures(IListFeaturesCallback listFeaturesCallback)
+ throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
+ Objects.requireNonNull(listFeaturesCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ listFeaturesCallback.onFailure(
+ OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ return;
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.run(
+ service -> service.listFeatures(Binder.getCallingUid(),
+ listFeaturesCallback));
}
- ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.post(
- service -> service.getFeatureDetails(Binder.getCallingUid(), feature,
- featureDetailsCallback));
- }
- @Override
- public void requestFeatureDownload(Feature feature, ICancellationSignal cancellationSignal,
- IDownloadCallback downloadCallback) throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal requestFeatureDownload");
- Objects.requireNonNull(feature);
- Objects.requireNonNull(downloadCallback);
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- downloadCallback.onDownloadFailed(
- DownloadCallback.DOWNLOAD_FAILURE_STATUS_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- new PersistableBundle());
+ @Override
+ public void getFeatureDetails(Feature feature,
+ IFeatureDetailsCallback featureDetailsCallback)
+ throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatureStatus");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(featureDetailsCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ featureDetailsCallback.onFailure(
+ OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ return;
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.run(
+ service -> service.getFeatureDetails(Binder.getCallingUid(), feature,
+ featureDetailsCallback));
}
- ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.post(
- service -> service.requestFeatureDownload(Binder.getCallingUid(), feature,
- cancellationSignal,
- downloadCallback));
- }
-
- @Override
- public void requestTokenInfo(Feature feature,
- Content request, ICancellationSignal cancellationSignal,
- ITokenInfoCallback tokenInfoCallback) throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal prepareFeatureProcessing");
- Objects.requireNonNull(feature);
- Objects.requireNonNull(request);
- Objects.requireNonNull(tokenInfoCallback);
-
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- tokenInfoCallback.onFailure(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- new PersistableBundle());
+ @Override
+ public void requestFeatureDownload(Feature feature,
+ ICancellationSignal cancellationSignal,
+ IDownloadCallback downloadCallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal requestFeatureDownload");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(downloadCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ downloadCallback.onDownloadFailed(
+ DownloadCallback.DOWNLOAD_FAILURE_STATUS_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.run(
+ service -> service.requestFeatureDownload(Binder.getCallingUid(), feature,
+ cancellationSignal,
+ downloadCallback));
}
- ensureRemoteInferenceServiceInitialized();
- mRemoteInferenceService.post(
- service -> service.requestTokenInfo(Binder.getCallingUid(), feature, request,
- cancellationSignal,
- tokenInfoCallback));
- }
- @Override
- public void processRequest(Feature feature,
- Content request,
- int requestType,
- ICancellationSignal cancellationSignal,
- IProcessingSignal processingSignal,
- IResponseCallback responseCallback)
- throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequest");
- Objects.requireNonNull(feature);
- Objects.requireNonNull(responseCallback);
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- responseCallback.onFailure(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- new PersistableBundle());
- }
- ensureRemoteInferenceServiceInitialized();
- mRemoteInferenceService.post(
- service -> service.processRequest(Binder.getCallingUid(), feature, request,
- requestType,
- cancellationSignal, processingSignal,
- responseCallback));
- }
- @Override
- public void processRequestStreaming(Feature feature,
- Content request,
- int requestType,
- ICancellationSignal cancellationSignal,
- IProcessingSignal processingSignal,
- IStreamingResponseCallback streamingCallback) throws RemoteException {
- Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequestStreaming");
- Objects.requireNonNull(feature);
- Objects.requireNonNull(streamingCallback);
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
- if (!mIsServiceEnabled) {
- Slog.w(TAG, "Service not available");
- streamingCallback.onFailure(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
- "OnDeviceIntelligenceManagerService is unavailable",
- new PersistableBundle());
+ @Override
+ public void requestTokenInfo(Feature feature,
+ Bundle request, ICancellationSignal cancellationSignal,
+ ITokenInfoCallback tokenInfoCallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal prepareFeatureProcessing");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(tokenInfoCallback);
+
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ tokenInfoCallback.onFailure(
+ OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ }
+ ensureRemoteInferenceServiceInitialized();
+ mRemoteInferenceService.run(
+ service -> service.requestTokenInfo(Binder.getCallingUid(), feature,
+ request,
+ cancellationSignal,
+ tokenInfoCallback));
}
- ensureRemoteInferenceServiceInitialized();
- mRemoteInferenceService.post(
- service -> service.processRequestStreaming(Binder.getCallingUid(), feature,
- request, requestType,
- cancellationSignal, processingSignal,
- streamingCallback));
- }
+
+ @Override
+ public void processRequest(Feature feature,
+ Bundle request,
+ int requestType,
+ ICancellationSignal cancellationSignal,
+ IProcessingSignal processingSignal,
+ IResponseCallback responseCallback)
+ throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequest");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(responseCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ responseCallback.onFailure(
+ OnDeviceIntelligenceException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ }
+ ensureRemoteInferenceServiceInitialized();
+ mRemoteInferenceService.run(
+ service -> service.processRequest(Binder.getCallingUid(), feature, request,
+ requestType,
+ cancellationSignal, processingSignal,
+ responseCallback));
+ }
+
+ @Override
+ public void processRequestStreaming(Feature feature,
+ Bundle request,
+ int requestType,
+ ICancellationSignal cancellationSignal,
+ IProcessingSignal processingSignal,
+ IStreamingResponseCallback streamingCallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequestStreaming");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(streamingCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ streamingCallback.onFailure(
+ OnDeviceIntelligenceException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ PersistableBundle.EMPTY);
+ }
+ ensureRemoteInferenceServiceInitialized();
+ mRemoteInferenceService.run(
+ service -> service.processRequestStreaming(Binder.getCallingUid(), feature,
+ request, requestType,
+ cancellationSignal, processingSignal,
+ streamingCallback));
+ }
+
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+ new OnDeviceIntelligenceShellCommand(OnDeviceIntelligenceManagerService.this).exec(
+ this, in, out, err, args, callback, resultReceiver);
+ }
+ };
}
private void ensureRemoteIntelligenceServiceInitialized() throws RemoteException {
synchronized (mLock) {
if (mRemoteOnDeviceIntelligenceService == null) {
- String serviceName = mContext.getResources().getString(
- R.string.config_defaultOnDeviceIntelligenceService);
+ String serviceName = getServiceNames()[0];
validateService(serviceName, false);
mRemoteOnDeviceIntelligenceService = new RemoteOnDeviceIntelligenceService(mContext,
ComponentName.unflattenFromString(serviceName),
@@ -352,13 +390,13 @@
IProcessingUpdateStatusCallback callback) {
try {
ensureRemoteInferenceServiceInitialized();
- mRemoteInferenceService.post(
+ mRemoteInferenceService.run(
service -> service.updateProcessingState(
processingState, callback));
} catch (RemoteException unused) {
try {
callback.onFailure(
- PROCESSING_UPDATE_STATUS_CONNECTION_FAILED,
+ OnDeviceIntelligenceException.PROCESSING_UPDATE_STATUS_CONNECTION_FAILED,
"Received failure invoking the remote processing service.");
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to send failure status.", ex);
@@ -371,8 +409,7 @@
private void ensureRemoteInferenceServiceInitialized() throws RemoteException {
synchronized (mLock) {
if (mRemoteInferenceService == null) {
- String serviceName = mContext.getResources().getString(
- R.string.config_defaultOnDeviceSandboxedInferenceService);
+ String serviceName = getServiceNames()[1];
validateService(serviceName, true);
mRemoteInferenceService = new RemoteOnDeviceSandboxedInferenceService(mContext,
ComponentName.unflattenFromString(serviceName),
@@ -384,7 +421,7 @@
@NonNull IOnDeviceSandboxedInferenceService service) {
try {
ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.post(
+ mRemoteOnDeviceIntelligenceService.run(
intelligenceService -> intelligenceService.notifyInferenceServiceConnected());
service.registerRemoteStorageService(
getIRemoteStorageService());
@@ -404,7 +441,7 @@
public void getReadOnlyFileDescriptor(
String filePath,
AndroidFuture<ParcelFileDescriptor> future) {
- mRemoteOnDeviceIntelligenceService.post(
+ mRemoteOnDeviceIntelligenceService.run(
service -> service.getReadOnlyFileDescriptor(
filePath, future));
}
@@ -413,7 +450,7 @@
public void getReadOnlyFeatureFileDescriptorMap(
Feature feature,
RemoteCallback remoteCallback) {
- mRemoteOnDeviceIntelligenceService.post(
+ mRemoteOnDeviceIntelligenceService.run(
service -> service.getReadOnlyFeatureFileDescriptorMap(
feature, remoteCallback));
}
@@ -469,4 +506,92 @@
return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
&& (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0;
}
+
+ @Nullable
+ public String getRemoteConfiguredPackageName() {
+ try {
+ String[] serviceNames = getServiceNames();
+ ComponentName componentName = ComponentName.unflattenFromString(serviceNames[1]);
+ if (componentName != null) {
+ return componentName.getPackageName();
+ }
+ } catch (Resources.NotFoundException e) {
+ Slog.e(TAG, "Could not find resource", e);
+ }
+
+ return null;
+ }
+
+
+ protected String[] getServiceNames() throws Resources.NotFoundException {
+ // TODO 329240495 : Consider a small class with explicit field names for the two services
+ synchronized (mLock) {
+ if (mTemporaryServiceNames != null && mTemporaryServiceNames.length == 2) {
+ return mTemporaryServiceNames;
+ }
+ }
+ return new String[]{mContext.getResources().getString(
+ R.string.config_defaultOnDeviceIntelligenceService),
+ mContext.getResources().getString(
+ R.string.config_defaultOnDeviceSandboxedInferenceService)};
+ }
+
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void setTemporaryServices(@NonNull String[] componentNames, int durationMs) {
+ Objects.requireNonNull(componentNames);
+ enforceShellOnly(Binder.getCallingUid(), "setTemporaryServices");
+ mContext.enforceCallingPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ synchronized (mLock) {
+ mTemporaryServiceNames = componentNames;
+
+ if (mTemporaryHandler == null) {
+ mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_RESET_TEMPORARY_SERVICE) {
+ synchronized (mLock) {
+ resetTemporaryServices();
+ }
+ } else {
+ Slog.wtf(TAG, "invalid handler msg: " + msg);
+ }
+ }
+ };
+ } else {
+ mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
+ }
+
+ if (durationMs != -1) {
+ mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs);
+ }
+ }
+ }
+
+ public void resetTemporaryServices() {
+ synchronized (mLock) {
+ if (mTemporaryHandler != null) {
+ mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
+ mTemporaryHandler = null;
+ }
+
+ mRemoteInferenceService = null;
+ mRemoteOnDeviceIntelligenceService = null;
+ mTemporaryServiceNames = new String[0];
+ }
+ }
+
+ /**
+ * Throws if the caller is not of a shell (or root) UID.
+ *
+ * @param callingUid pass Binder.callingUid().
+ */
+ public static void enforceShellOnly(int callingUid, String message) {
+ if (callingUid == android.os.Process.SHELL_UID
+ || callingUid == android.os.Process.ROOT_UID) {
+ return; // okay
+ }
+
+ throw new SecurityException(message + ": Only shell user can call it");
+ }
}
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
new file mode 100644
index 0000000..a76d8a3
--- /dev/null
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
@@ -0,0 +1,96 @@
+/*
+ * 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.ondeviceintelligence;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+final class OnDeviceIntelligenceShellCommand extends ShellCommand {
+ private static final String TAG = OnDeviceIntelligenceShellCommand.class.getSimpleName();
+
+ @NonNull
+ private final OnDeviceIntelligenceManagerService mService;
+
+ OnDeviceIntelligenceShellCommand(@NonNull OnDeviceIntelligenceManagerService service) {
+ mService = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+
+ switch (cmd) {
+ case "set-temporary-services":
+ return setTemporaryServices();
+ case "get-services":
+ return getConfiguredServices();
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ }
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("OnDeviceIntelligenceShellCommand commands: ");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println();
+ pw.println(
+ " set-temporary-services [IntelligenceServiceComponentName] "
+ + "[InferenceServiceComponentName] [DURATION]");
+ pw.println(" Temporarily (for DURATION ms) changes the service implementations.");
+ pw.println(" To reset, call without any arguments.");
+
+ pw.println(" get-services To get the names of services that are currently being used.");
+ }
+
+ private int setTemporaryServices() {
+ final PrintWriter out = getOutPrintWriter();
+ final String intelligenceServiceName = getNextArg();
+ final String inferenceServiceName = getNextArg();
+ if (getRemainingArgsCount() == 0 && intelligenceServiceName == null
+ && inferenceServiceName == null) {
+ mService.resetTemporaryServices();
+ out.println("OnDeviceIntelligenceManagerService temporary reset. ");
+ return 0;
+ }
+
+ Objects.requireNonNull(intelligenceServiceName);
+ Objects.requireNonNull(inferenceServiceName);
+ final int duration = Integer.parseInt(getNextArgRequired());
+ mService.setTemporaryServices(
+ new String[]{intelligenceServiceName, inferenceServiceName}, duration);
+ out.println("OnDeviceIntelligenceService temporarily set to " + intelligenceServiceName
+ + " \n and \n OnDeviceTrustedInferenceService set to " + inferenceServiceName
+ + " for " + duration + "ms");
+ return 0;
+ }
+
+ private int getConfiguredServices() {
+ final PrintWriter out = getOutPrintWriter();
+ String[] services = mService.getServiceNames();
+ out.println("OnDeviceIntelligenceService set to : " + services[0]
+ + " \n and \n OnDeviceTrustedInferenceService set to : " + services[1]);
+ return 0;
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/permission/OWNERS b/services/core/java/com/android/server/permission/OWNERS
new file mode 100644
index 0000000..fb6099c
--- /dev/null
+++ b/services/core/java/com/android/server/permission/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 137825
+
+include platform/frameworks/base:/core/java/android/permission/OWNERS
diff --git a/services/core/java/com/android/server/permission/PermissionManagerLocal.java b/services/core/java/com/android/server/permission/PermissionManagerLocal.java
new file mode 100644
index 0000000..7251e6e
--- /dev/null
+++ b/services/core/java/com/android/server/permission/PermissionManagerLocal.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.permission;
+
+import android.annotation.TestApi;
+import com.android.internal.annotations.Keep;
+
+/**
+ * In-process API for server side permission related infrastructure.
+ *
+ * @hide
+ */
+@Keep
+@TestApi
+public interface PermissionManagerLocal {
+
+ /**
+ * Get whether signature permission allowlist is enforced even on debuggable builds.
+ *
+ * @return whether the signature permission allowlist is force enforced
+ */
+ @TestApi
+ boolean isSignaturePermissionAllowlistForceEnforced();
+
+ /**
+ * Set whether signature permission allowlist is enforced even on debuggable builds.
+ *
+ * @param forceEnforced whether the signature permission allowlist is force enforced
+ */
+ @TestApi
+ void setSignaturePermissionAllowlistForceEnforced(boolean forceEnforced);
+}
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 9af2b3f..278deb8 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -207,18 +207,8 @@
+ intent.toShortString(false, true, false, false)
+ " " + intent.getExtras(), here);
}
- final boolean ordered;
- if (mAmInternal.isModernQueueEnabled()) {
- // When the modern broadcast stack is enabled, deliver all our
- // broadcasts as unordered, since the modern stack has better
- // support for sequencing cold-starts, and it supports
- // delivering resultTo for non-ordered broadcasts
- ordered = false;
- } else {
- ordered = (finishedReceiver != null);
- }
- mAmInternal.broadcastIntent(
- intent, finishedReceiver, requiredPermissions, ordered, userId,
+ mAmInternal.broadcastIntentWithCallback(
+ intent, finishedReceiver, requiredPermissions, userId,
broadcastAllowList == null ? null : broadcastAllowList.get(userId),
filterExtrasForReceiver, bOptions);
}
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 3abf3a5..ecfc768 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -50,13 +50,17 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApexStagedEvent;
import android.content.pm.Flags;
+import android.content.pm.IPackageManagerNative;
+import android.content.pm.IStagedApexObserver;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.dex.ArtManager;
import android.os.Binder;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
@@ -1054,6 +1058,10 @@
artManager.scheduleBackgroundDexoptJob();
}
}, new IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED));
+
+ if (Flags.useArtServiceV2()) {
+ StagedApexObserver.registerForStagedApexUpdates(artManager);
+ }
}
/**
@@ -1168,4 +1176,32 @@
&& dexoptOptions.isCompilationEnabled()
&& !isApex;
}
+
+ private static class StagedApexObserver extends IStagedApexObserver.Stub {
+ private final @NonNull ArtManagerLocal mArtManager;
+
+ static void registerForStagedApexUpdates(@NonNull ArtManagerLocal artManager) {
+ IPackageManagerNative packageNative = IPackageManagerNative.Stub.asInterface(
+ ServiceManager.getService("package_native"));
+ if (packageNative == null) {
+ Log.e(TAG, "No IPackageManagerNative");
+ return;
+ }
+
+ try {
+ packageNative.registerStagedApexObserver(new StagedApexObserver(artManager));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register staged apex observer", e);
+ }
+ }
+
+ private StagedApexObserver(@NonNull ArtManagerLocal artManager) {
+ mArtManager = artManager;
+ }
+
+ @Override
+ public void onApexStaged(@NonNull ApexStagedEvent event) {
+ mArtManager.onApexStaged(event.stagedApexModuleNames);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index c7ebb3c..c6bb99e 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -2116,6 +2116,18 @@
@RequiresPermission(READ_FRAME_BUFFER)
@Override
+ public void saveViewCaptureData() {
+ int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER);
+ if (PERMISSION_GRANTED == status) {
+ forEachViewCaptureWindow(this::dumpViewCaptureDataToWmTrace);
+ } else {
+ Log.w(TAG, "caller lacks permissions to save view capture data");
+ }
+ }
+
+
+ @RequiresPermission(READ_FRAME_BUFFER)
+ @Override
public void registerDumpCallback(@NonNull IDumpCallback cb) {
int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER);
if (PERMISSION_GRANTED == status) {
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 29de26e..ec98fff 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -557,6 +557,28 @@
}
/**
+ * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
+ * This allows the badging to be done based on the actual bitmap size rather than
+ * the scaled bitmap size.
+ */
+ private static class FixedSizeBitmapDrawable extends BitmapDrawable {
+
+ FixedSizeBitmapDrawable(@Nullable final Bitmap bitmap) {
+ super(null, bitmap);
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return getBitmap().getWidth();
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return getBitmap().getWidth();
+ }
+ }
+
+ /**
* Create an <a
* href="https://developer.android.com/develop/ui/views/launch/icon_design_adaptive">
* adaptive icon</a> from an icon.
@@ -569,6 +591,11 @@
}
// see BaseIconFactory#createShapedIconBitmap
+ if (iconDrawable instanceof BitmapDrawable) {
+ var icon = ((BitmapDrawable) iconDrawable).getBitmap();
+ iconDrawable = new FixedSizeBitmapDrawable(icon);
+ }
+
float inset = getExtraInsetFraction();
inset = inset / (1 + 2 * inset);
Drawable d = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK),
diff --git a/services/core/java/com/android/server/pm/PackageManagerLocal.java b/services/core/java/com/android/server/pm/PackageManagerLocal.java
index 6266ef3..4ed6e80 100644
--- a/services/core/java/com/android/server/pm/PackageManagerLocal.java
+++ b/services/core/java/com/android/server/pm/PackageManagerLocal.java
@@ -20,6 +20,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.content.pm.SigningDetails;
import android.os.Binder;
import android.os.UserHandle;
@@ -124,6 +126,48 @@
FilteredSnapshot withFilteredSnapshot(int callingUid, @NonNull UserHandle user);
/**
+ * Add a pair of signing details so that packages signed with {@code oldSigningDetails} will
+ * behave as if they are signed by the {@code newSigningDetails}.
+ * <p>
+ * This is only available on {@link android.os.Build#isDebuggable debuggable} builds.
+ *
+ * @param oldSigningDetails the original signing detail of the package
+ * @param newSigningDetails the new signing detail that will replace the original one
+ * @throws SecurityException if the build is not debuggable
+ *
+ * @hide
+ */
+ @TestApi
+ void addOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails,
+ @NonNull SigningDetails newSigningDetails);
+
+ /**
+ * Remove a pair of signing details previously added via {@link #addOverrideSigningDetails} by
+ * the old signing details.
+ * <p>
+ * This is only available on {@link android.os.Build#isDebuggable debuggable} builds.
+ *
+ * @param oldSigningDetails the original signing detail of the package
+ * @throws SecurityException if the build is not debuggable
+ *
+ * @hide
+ */
+ @TestApi
+ void removeOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails);
+
+ /**
+ * Clear all pairs of signing details previously added via {@link #addOverrideSigningDetails}.
+ * <p>
+ * This is only available on {@link android.os.Build#isDebuggable debuggable} builds.
+ *
+ * @throws SecurityException if the build is not debuggable
+ *
+ * @hide
+ */
+ @TestApi
+ void clearOverrideSigningDetails();
+
+ /**
* @hide
*/
@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index fe8030b..7c51707 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -38,7 +38,6 @@
import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
-import static android.util.FeatureFlagUtils.SETTINGS_TREAT_PAUSE_AS_QUARANTINE;
import static com.android.internal.annotations.VisibleForTesting.Visibility;
import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_INIT_TIME;
@@ -168,7 +167,6 @@
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.ExceptionUtils;
-import android.util.FeatureFlagUtils;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -3208,15 +3206,8 @@
}
if (quarantined) {
- final boolean hasQuarantineAppsPerm = mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.QUARANTINE_APPS) == PERMISSION_GRANTED;
- // TODO: b/305256093 - In order to facilitate testing, temporarily allowing apps
- // with SUSPEND_APPS permission to quarantine apps. Remove this once the testing
- // is done and this is no longer needed.
- if (!hasQuarantineAppsPerm) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SUSPEND_APPS,
- callingMethod);
- }
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.QUARANTINE_APPS,
+ callingMethod);
} else {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SUSPEND_APPS,
callingMethod);
@@ -6287,16 +6278,8 @@
SuspendDialogInfo dialogInfo, int flags, String suspendingPackage,
int suspendingUserId, int targetUserId) {
final int callingUid = Binder.getCallingUid();
- boolean quarantined = false;
- if (Flags.quarantinedEnabled()) {
- if ((flags & PackageManager.FLAG_SUSPEND_QUARANTINED) != 0) {
- quarantined = true;
- } else if (FeatureFlagUtils.isEnabled(mContext,
- SETTINGS_TREAT_PAUSE_AS_QUARANTINE)) {
- final String wellbeingPkg = mContext.getString(R.string.config_systemWellbeing);
- quarantined = suspendingPackage.equals(wellbeingPkg);
- }
- }
+ final boolean quarantined = ((flags & PackageManager.FLAG_SUSPEND_QUARANTINED) != 0)
+ && Flags.quarantinedEnabled();
final Computer snapshot = snapshotComputer();
final UserPackage suspender = UserPackage.of(targetUserId, suspendingPackage);
enforceCanSetPackagesSuspendedAsUser(snapshot, quarantined, suspender, callingUid,
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 59d6219..e35a169 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2710,7 +2710,8 @@
} catch (java.io.IOException e) {
mReadMessages.append("Error reading: " + e.toString());
- PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e);
+ PackageManagerService.reportSettingsProblem(Log.ERROR,
+ "Error reading stopped packages: " + e);
Slog.wtf(PackageManagerService.TAG, "Error reading package manager stopped packages",
e);
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index c2f74a8..c9fd261 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -155,7 +155,8 @@
UserManager.DISALLOW_CONFIG_DEFAULT_APPS,
UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO,
UserManager.DISALLOW_SIM_GLOBALLY,
- UserManager.DISALLOW_ASSIST_CONTENT
+ UserManager.DISALLOW_ASSIST_CONTENT,
+ UserManager.DISALLOW_THREAD_NETWORK
});
public static final Set<String> DEPRECATED_USER_RESTRICTIONS = Sets.newArraySet(
@@ -206,7 +207,8 @@
UserManager.DISALLOW_ADD_WIFI_CONFIG,
UserManager.DISALLOW_CELLULAR_2G,
UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO,
- UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO
+ UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO,
+ UserManager.DISALLOW_THREAD_NETWORK
);
/**
@@ -252,7 +254,8 @@
UserManager.DISALLOW_ADD_WIFI_CONFIG,
UserManager.DISALLOW_CELLULAR_2G,
UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO,
- UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO
+ UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO,
+ UserManager.DISALLOW_THREAD_NETWORK
);
/**
diff --git a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
index 8d05450..55afb17 100644
--- a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
+++ b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
@@ -20,9 +20,12 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.content.pm.SigningDetails;
import android.os.Binder;
+import android.os.Build;
import android.os.UserHandle;
import android.util.ArrayMap;
+import android.util.apk.ApkSignatureVerifier;
import com.android.server.pm.Computer;
import com.android.server.pm.PackageManagerLocal;
@@ -72,6 +75,31 @@
mService.snapshotComputer(false /*allowLiveComputer*/), null);
}
+ @Override
+ public void addOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails,
+ @NonNull SigningDetails newSigningDetails) {
+ if (!Build.isDebuggable()) {
+ throw new SecurityException("This test API is only available on debuggable builds");
+ }
+ ApkSignatureVerifier.addOverrideSigningDetails(oldSigningDetails, newSigningDetails);
+ }
+
+ @Override
+ public void removeOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails) {
+ if (!Build.isDebuggable()) {
+ throw new SecurityException("This test API is only available on debuggable builds");
+ }
+ ApkSignatureVerifier.removeOverrideSigningDetails(oldSigningDetails);
+ }
+
+ @Override
+ public void clearOverrideSigningDetails() {
+ if (!Build.isDebuggable()) {
+ throw new SecurityException("This test API is only available on debuggable builds");
+ }
+ ApkSignatureVerifier.clearOverrideSigningDetails();
+ }
+
private abstract static class BaseSnapshotImpl implements AutoCloseable {
private boolean mClosed;
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index 1b220a0..453c6ef 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -16,7 +16,7 @@
package com.android.server.policy;
-import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE_IDENTIFIER;
import android.annotation.NonNull;
@@ -45,9 +45,9 @@
import com.android.server.input.InputManagerInternal;
import com.android.server.policy.devicestate.config.Conditions;
import com.android.server.policy.devicestate.config.DeviceStateConfig;
-import com.android.server.policy.devicestate.config.Flags;
import com.android.server.policy.devicestate.config.LidSwitchCondition;
import com.android.server.policy.devicestate.config.NumericRange;
+import com.android.server.policy.devicestate.config.Properties;
import com.android.server.policy.devicestate.config.SensorCondition;
import com.android.server.policy.devicestate.config.XmlParser;
@@ -63,8 +63,10 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.function.BooleanSupplier;
import javax.xml.datatype.DatatypeConfigurationException;
@@ -94,21 +96,49 @@
private static final BooleanSupplier FALSE_BOOLEAN_SUPPLIER = () -> false;
@VisibleForTesting
- static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(MINIMUM_DEVICE_STATE_IDENTIFIER,
- "DEFAULT", 0 /* flags */);
+ static final DeviceState DEFAULT_DEVICE_STATE =
+ new DeviceState(new DeviceState.Configuration.Builder(MINIMUM_DEVICE_STATE_IDENTIFIER,
+ "DEFAULT").build());
private static final String VENDOR_CONFIG_FILE_PATH = "etc/devicestate/";
private static final String DATA_CONFIG_FILE_PATH = "system/devicestate/";
private static final String CONFIG_FILE_NAME = "device_state_configuration.xml";
- private static final String FLAG_CANCEL_OVERRIDE_REQUESTS = "FLAG_CANCEL_OVERRIDE_REQUESTS";
- private static final String FLAG_APP_INACCESSIBLE = "FLAG_APP_INACCESSIBLE";
- private static final String FLAG_EMULATED_ONLY = "FLAG_EMULATED_ONLY";
- private static final String FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP =
- "FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP";
- private static final String FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL =
- "FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL";
- private static final String FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE =
- "FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE";
+ private static final String PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED =
+ "com.android.server.policy.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED";
+ private static final String PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN =
+ "com.android.server.policy.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN";
+ private static final String PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN =
+ "com.android.server.policy.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN";
+ private static final String PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS =
+ "com.android.server.policy.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS";
+ private static final String PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP =
+ "com.android.server.policy.PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP";
+ private static final String PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL =
+ "com.android.server.policy.PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL";
+ private static final String PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE =
+ "com.android.server.policy.PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE";
+ private static final String PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST =
+ "com.android.server.policy.PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST";
+ private static final String PROPERTY_APP_INACCESSIBLE =
+ "com.android.server.policy.PROPERTY_APP_INACCESSIBLE";
+ private static final String PROPERTY_EMULATED_ONLY =
+ "com.android.server.policy.PROPERTY_EMULATED_ONLY";
+ private static final String PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY =
+ "com.android.server.policy.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY";
+ private static final String PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY =
+ "com.android.server.policy.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY";
+ private static final String PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP =
+ "com.android.server.policy.PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP";
+ private static final String PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE =
+ "com.android.server.policy.PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE";
+ private static final String PROPERTY_EXTENDED_DEVICE_STATE_EXTERNAL_DISPLAY =
+ "com.android.server.policy.PROPERTY_EXTENDED_DEVICE_STATE_EXTERNAL_DISPLAY";
+ private static final String PROPERTY_FEATURE_REAR_DISPLAY =
+ "com.android.server.policy.PROPERTY_FEATURE_REAR_DISPLAY";
+ private static final String PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT =
+ "com.android.server.policy.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT";
+
+
/** Interface that allows reading the device state configuration. */
interface ReadableConfig {
@@ -149,40 +179,25 @@
final int state = stateConfig.getIdentifier().intValue();
final String name = stateConfig.getName() == null ? "" : stateConfig.getName();
- int flags = 0;
- final Flags configFlags = stateConfig.getFlags();
+ Set<@DeviceState.DeviceStateProperties Integer> systemProperties =
+ new HashSet<>();
+ Set<@DeviceState.DeviceStateProperties Integer> physicalProperties =
+ new HashSet<>();
+ final Properties configFlags = stateConfig.getProperties();
if (configFlags != null) {
- List<String> configFlagStrings = configFlags.getFlag();
- for (int i = 0; i < configFlagStrings.size(); i++) {
- final String configFlagString = configFlagStrings.get(i);
- switch (configFlagString) {
- case FLAG_CANCEL_OVERRIDE_REQUESTS:
- flags |= DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
- break;
- case FLAG_APP_INACCESSIBLE:
- flags |= DeviceState.FLAG_APP_INACCESSIBLE;
- break;
- case FLAG_EMULATED_ONLY:
- flags |= DeviceState.FLAG_EMULATED_ONLY;
- break;
- case FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP:
- flags |= DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP;
- break;
- case FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL:
- flags |= DeviceState
- .FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL;
- break;
- case FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE:
- flags |= DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE;
- default:
- Slog.w(TAG, "Parsed unknown flag with name: "
- + configFlagString);
- break;
- }
+ List<String> configPropertyStrings = configFlags.getProperty();
+ for (int i = 0; i < configPropertyStrings.size(); i++) {
+ final String configPropertyString = configPropertyStrings.get(i);
+ addPropertyByString(configPropertyString, systemProperties,
+ physicalProperties);
}
}
-
- deviceStateList.add(new DeviceState(state, name, flags));
+ DeviceState.Configuration deviceStateConfiguration =
+ new DeviceState.Configuration.Builder(state, name)
+ .setSystemProperties(systemProperties)
+ .setPhysicalProperties(physicalProperties)
+ .build();
+ deviceStateList.add(new DeviceState(deviceStateConfiguration));
final Conditions condition = stateConfig.getConditions();
conditionsList.add(condition);
@@ -190,13 +205,88 @@
}
}
- if (deviceStateList.size() == 0) {
+ if (deviceStateList.isEmpty()) {
deviceStateList.add(DEFAULT_DEVICE_STATE);
conditionsList.add(null);
}
return new DeviceStateProviderImpl(context, deviceStateList, conditionsList);
}
+ private static void addPropertyByString(String propertyString,
+ Set<@DeviceState.SystemDeviceStateProperties Integer> systemProperties,
+ Set<@DeviceState.PhysicalDeviceStateProperties Integer> physicalProperties) {
+ switch (propertyString) {
+ // Look for the physical hardware properties first
+ case PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED:
+ physicalProperties.add(
+ DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED);
+ break;
+ case PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN:
+ physicalProperties.add(
+ DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN);
+ break;
+ case PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN:
+ physicalProperties.add(
+ DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN);
+ break;
+ case PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS:
+ systemProperties.add(
+ DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS);
+ break;
+ case PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP:
+ systemProperties.add(
+ DeviceState.PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
+ break;
+ case PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL:
+ systemProperties.add(
+ DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL);
+ break;
+ case PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE:
+ systemProperties.add(
+ DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE);
+ break;
+ case PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST:
+ systemProperties.add(
+ DeviceState.PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST);
+ break;
+ case PROPERTY_APP_INACCESSIBLE:
+ systemProperties.add(DeviceState.PROPERTY_APP_INACCESSIBLE);
+ break;
+ case PROPERTY_EMULATED_ONLY:
+ systemProperties.add(DeviceState.PROPERTY_EMULATED_ONLY);
+ break;
+ case PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY:
+ systemProperties.add(
+ DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY);
+ break;
+ case PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY:
+ systemProperties.add(
+ DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY);
+ break;
+ case PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP:
+ systemProperties.add(
+ DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP);
+ break;
+ case PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE:
+ systemProperties.add(
+ DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE);
+ break;
+ case PROPERTY_EXTENDED_DEVICE_STATE_EXTERNAL_DISPLAY:
+ systemProperties.add(
+ DeviceState.PROPERTY_EXTENDED_DEVICE_STATE_EXTERNAL_DISPLAY);
+ break;
+ case PROPERTY_FEATURE_REAR_DISPLAY:
+ systemProperties.add(DeviceState.PROPERTY_FEATURE_REAR_DISPLAY);
+ break;
+ case PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT:
+ systemProperties.add(DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT);
+ break;
+ default:
+ Slog.w(TAG, "Parsed unknown flag with name: " + propertyString);
+ break;
+ }
+ }
+
// Lock for internal state.
private final Object mLock = new Object();
private final Context mContext;
@@ -210,7 +300,7 @@
@GuardedBy("mLock")
private Listener mListener = null;
@GuardedBy("mLock")
- private int mLastReportedState = INVALID_DEVICE_STATE;
+ private int mLastReportedState = INVALID_DEVICE_STATE_IDENTIFIER;
@GuardedBy("mLock")
private Boolean mIsLidOpen;
@@ -285,7 +375,7 @@
if (conditions == null) {
// If this state has the FLAG_EMULATED_ONLY flag on it, it should never be triggered
// by a physical hardware change, and should always return false for it's conditions
- if (deviceStates.get(i).hasFlag(DeviceState.FLAG_EMULATED_ONLY)) {
+ if (deviceStates.get(i).hasProperty(DeviceState.PROPERTY_EMULATED_ONLY)) {
mStateConditions.put(state, FALSE_BOOLEAN_SUPPLIER);
} else {
mStateConditions.put(state, TRUE_BOOLEAN_SUPPLIER);
@@ -410,13 +500,13 @@
}
listener = mListener;
for (DeviceState deviceState : mOrderedStates) {
- if (isThermalStatusCriticalOrAbove(mThermalStatus)
- && deviceState.hasFlag(
- DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) {
+ if (isThermalStatusCriticalOrAbove(mThermalStatus) && deviceState.hasProperty(
+ DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
+ )) {
continue;
}
- if (mPowerSaveModeEnabled && deviceState.hasFlag(
- DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) {
+ if (mPowerSaveModeEnabled && deviceState.hasProperty(
+ DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) {
continue;
}
supportedStates.add(deviceState);
@@ -424,18 +514,19 @@
}
listener.onSupportedDeviceStatesChanged(
- supportedStates.toArray(new DeviceState[supportedStates.size()]), reason);
+ supportedStates.toArray(new DeviceState[supportedStates.size()]),
+ reason);
}
/** Computes the current device state and notifies the listener of a change, if needed. */
void notifyDeviceStateChangedIfNeeded() {
- int stateToReport = INVALID_DEVICE_STATE;
+ int stateToReport = INVALID_DEVICE_STATE_IDENTIFIER;
synchronized (mLock) {
if (mListener == null) {
return;
}
- int newState = INVALID_DEVICE_STATE;
+ int newState = INVALID_DEVICE_STATE_IDENTIFIER;
for (int i = 0; i < mOrderedStates.length; i++) {
int state = mOrderedStates[i].getIdentifier();
if (DEBUG) {
@@ -464,18 +555,18 @@
break;
}
}
- if (newState == INVALID_DEVICE_STATE) {
+ if (newState == INVALID_DEVICE_STATE_IDENTIFIER) {
Slog.e(TAG, "No declared device states match any of the required conditions.");
dumpSensorValues();
}
- if (newState != INVALID_DEVICE_STATE && newState != mLastReportedState) {
+ if (newState != INVALID_DEVICE_STATE_IDENTIFIER && newState != mLastReportedState) {
mLastReportedState = newState;
stateToReport = newState;
}
}
- if (stateToReport != INVALID_DEVICE_STATE) {
+ if (stateToReport != INVALID_DEVICE_STATE_IDENTIFIER) {
mListener.onStateChanged(stateToReport);
}
}
@@ -774,8 +865,9 @@
}
private static boolean hasThermalSensitiveState(List<DeviceState> deviceStates) {
- for (DeviceState state : deviceStates) {
- if (state.hasFlag(DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) {
+ for (int i = 0; i < deviceStates.size(); i++) {
+ if (deviceStates.get(i).hasProperty(
+ DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) {
return true;
}
}
@@ -784,7 +876,8 @@
private static boolean hasPowerSaveSensitiveState(List<DeviceState> deviceStates) {
for (int i = 0; i < deviceStates.size(); i++) {
- if (deviceStates.get(i).hasFlag(DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) {
+ if (deviceStates.get(i).hasProperty(
+ DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) {
return true;
}
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ec4b38b..266418f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3523,7 +3523,8 @@
case KeyEvent.KEYCODE_DPAD_LEFT:
if (firstDown && event.isMetaPressed()) {
if (event.isCtrlPressed()) {
- enterStageSplitFromRunningApp(true /* leftOrTop */);
+ moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event),
+ true /* leftOrTop */);
logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
} else {
logKeyboardSystemsEvent(event, KeyboardLogEvent.BACK);
@@ -3534,7 +3535,8 @@
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
- enterStageSplitFromRunningApp(false /* leftOrTop */);
+ moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event),
+ false /* leftOrTop */);
logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
return true;
}
@@ -4313,6 +4315,7 @@
boolean allowDuringSetup) {
if (allowDuringSetup || isUserSetupComplete()) {
mContext.startActivityAsUser(intent, bundle, handle);
+ dismissKeyboardShortcutsMenu();
} else {
Slog.i(TAG, "Not starting activity because user setup is in progress: " + intent);
}
@@ -4363,6 +4366,7 @@
if (statusbar != null) {
statusbar.showRecentApps(triggeredFromAltTab);
}
+ dismissKeyboardShortcutsMenu();
}
private void toggleKeyboardShortcutsMenu(int deviceId) {
@@ -4387,10 +4391,10 @@
}
}
- private void enterStageSplitFromRunningApp(boolean leftOrTop) {
+ private void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop) {
StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
if (statusbar != null) {
- statusbar.enterStageSplitFromRunningApp(leftOrTop);
+ statusbar.moveFocusedTaskToStageSplit(displayId, leftOrTop);
}
}
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 652cf18..fde49d2 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -902,9 +902,9 @@
}
if (mActivityManagerInternal.isSystemReady()) {
- final boolean ordered = !mActivityManagerInternal.isModernQueueEnabled();
- mActivityManagerInternal.broadcastIntent(mScreenOnIntent, mWakeUpBroadcastDone,
- null, ordered, UserHandle.USER_ALL, null, null, mScreenOnOffOptions);
+ mActivityManagerInternal.broadcastIntentWithCallback(mScreenOnIntent,
+ mWakeUpBroadcastDone, null, UserHandle.USER_ALL,
+ null, null, mScreenOnOffOptions);
} else {
EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 2, 1);
sendNextBroadcast();
@@ -927,9 +927,9 @@
}
if (mActivityManagerInternal.isSystemReady()) {
- final boolean ordered = !mActivityManagerInternal.isModernQueueEnabled();
- mActivityManagerInternal.broadcastIntent(mScreenOffIntent, mGoToSleepBroadcastDone,
- null, ordered, UserHandle.USER_ALL, null, null, mScreenOnOffOptions);
+ mActivityManagerInternal.broadcastIntentWithCallback(mScreenOffIntent,
+ mGoToSleepBroadcastDone, null, UserHandle.USER_ALL,
+ null, null, mScreenOnOffOptions);
} else {
EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 3, 1);
sendNextBroadcast();
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index a172de0..b50e2bf 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -7149,7 +7149,7 @@
* Any changes to the device state are treated as user interactions.
*/
class DeviceStateListener implements DeviceStateManager.DeviceStateCallback {
- private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
+ private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
@Override
public void onStateChanged(int deviceState) {
diff --git a/services/core/java/com/android/server/rollback/OWNERS b/services/core/java/com/android/server/rollback/OWNERS
index daa0211..8337fd2 100644
--- a/services/core/java/com/android/server/rollback/OWNERS
+++ b/services/core/java/com/android/server/rollback/OWNERS
@@ -1,3 +1 @@
-ancr@google.com
-harshitmahajan@google.com
-robertogil@google.com
+include /services/core/java/com/android/server/crashrecovery/OWNERS
\ No newline at end of file
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 14e0ce1..c73f89c 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -233,9 +233,9 @@
/**
* Enters stage split from a current running app.
*
- * @see com.android.internal.statusbar.IStatusBar#enterStageSplitFromRunningApp
+ * @see com.android.internal.statusbar.IStatusBar#moveFocusedTaskToStageSplit
*/
- void enterStageSplitFromRunningApp(boolean leftOrTop);
+ void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop);
/**
* Shows the media output switcher dialog.
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 0b48a75..214dbe0 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -820,11 +820,11 @@
}
@Override
- public void enterStageSplitFromRunningApp(boolean leftOrTop) {
+ public void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop) {
IStatusBar bar = mBar;
if (bar != null) {
try {
- bar.enterStageSplitFromRunningApp(leftOrTop);
+ bar.moveFocusedTaskToStageSplit(displayId, leftOrTop);
} catch (RemoteException ex) { }
}
}
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index 79adcb4..34c90f1 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -349,6 +349,7 @@
}
}
+ userState.mAdServiceMap.clear();
userState.mAdServiceMap = adServiceMap;
}
@@ -988,7 +989,7 @@
return;
}
UserState userState = getOrCreateUserStateLocked(resolvedUserId);
- TvAdServiceState adState = userState.mAdMap.get(serviceId);
+ TvAdServiceState adState = userState.mAdServiceMap.get(serviceId);
if (adState == null) {
Slogf.w(TAG, "Failed to find state for serviceId=" + serviceId);
sendAdSessionTokenToClientLocked(client, serviceId, null, null, seq);
@@ -3031,6 +3032,7 @@
ITvAdService service, IBinder sessionToken, int userId) {
UserState userState = getOrCreateUserStateLocked(userId);
AdSessionState sessionState = userState.mAdSessionStateMap.get(sessionToken);
+
if (DEBUG) {
Slogf.d(TAG, "createAdSessionInternalLocked(iAppServiceId="
+ sessionState.mAdServiceId + ")");
@@ -3300,8 +3302,6 @@
private static final class UserState {
private final int mUserId;
- // A mapping from the TV AD service ID to its TvAdServiceState.
- private Map<String, TvAdServiceState> mAdMap = new HashMap<>();
// A mapping from the name of a TV Interactive App service to its state.
private final Map<ComponentName, AdServiceState> mAdServiceStateMap = new HashMap<>();
// A mapping from the token of a TV Interactive App session to its state.
diff --git a/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java b/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java
index fb5140d..48dd992 100644
--- a/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java
@@ -51,8 +51,8 @@
public List<Step> cancel() {
if (mCancelled) {
// Double cancelling will just turn off the vibrator right away.
- return Arrays.asList(
- new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(), controller));
+ return Arrays.asList(new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(),
+ controller, /* isCleanUp= */ true));
}
return super.cancel();
}
@@ -92,8 +92,8 @@
} else {
// Vibration is completing normally, turn off after the deadline in case we
// don't receive the callback in time (callback also triggers it right away).
- return Arrays.asList(new TurnOffVibratorStep(
- conductor, mPendingVibratorOffDeadline, controller));
+ return Arrays.asList(new TurnOffVibratorStep(conductor,
+ mPendingVibratorOffDeadline, controller, /* isCleanUp= */ false));
}
}
diff --git a/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java b/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java
index 84da9f2..f40c994 100644
--- a/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java
@@ -44,8 +44,8 @@
@Override
public List<Step> cancel() {
- return Arrays.asList(
- new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(), controller));
+ return Arrays.asList(new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(),
+ controller, /* isCleanUp= */ true));
}
@Override
@@ -71,8 +71,8 @@
// Vibrator amplitude cannot go further down, just turn it off with the configured
// deadline that has been adjusted for the scenario when this was triggered by a
// cancelled vibration.
- return Arrays.asList(new TurnOffVibratorStep(
- conductor, mPendingVibratorOffDeadline, controller));
+ return Arrays.asList(new TurnOffVibratorStep(conductor, mPendingVibratorOffDeadline,
+ controller, /* isCleanUp= */ true));
}
return Arrays.asList(new RampOffVibratorStep(
conductor,
diff --git a/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java b/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java
index 297ef56..065ce11 100644
--- a/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java
@@ -32,20 +32,23 @@
*/
final class TurnOffVibratorStep extends AbstractVibratorStep {
+ private final boolean mIsCleanUp;
+
TurnOffVibratorStep(VibrationStepConductor conductor, long startTime,
- VibratorController controller) {
+ VibratorController controller, boolean isCleanUp) {
super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1, startTime);
+ mIsCleanUp = isCleanUp;
}
@Override
public boolean isCleanUp() {
- return true;
+ return mIsCleanUp;
}
@Override
public List<Step> cancel() {
- return Arrays.asList(
- new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(), controller));
+ return Arrays.asList(new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(),
+ controller, /* isCleanUp= */ true));
}
@Override
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index b490f57..6fc9d9a 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -208,6 +208,7 @@
* potentially expensive or resource-linked objects, such as {@link IBinder}.
*/
static final class DebugInfo {
+ final Status mStatus;
final long mCreateTime;
final CallerInfo mCallerInfo;
@Nullable
@@ -220,7 +221,6 @@
private final CombinedVibration mOriginalEffect;
private final int mScaleLevel;
private final float mAdaptiveScale;
- private final Status mStatus;
DebugInfo(Status status, VibrationStats stats, @Nullable CombinedVibration playedEffect,
@Nullable CombinedVibration originalEffect, int scaleLevel,
@@ -253,6 +253,10 @@
+ ", callerInfo: " + mCallerInfo;
}
+ void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
+ statsLogger.logVibrationAdaptiveHapticScale(mCallerInfo.uid, mAdaptiveScale);
+ }
+
/**
* Write this info in a compact way into given {@link PrintWriter}.
*
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index f510b4e..f3e226e 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -40,8 +40,10 @@
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
+import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
/**
* Creates and manages a queue of steps for performing a VibrationEffect, as well as coordinating
@@ -70,6 +72,7 @@
private final DeviceAdapter mDeviceAdapter;
private final VibrationScaler mVibrationScaler;
+ private final VibratorFrameworkStatsLogger mStatsLogger;
// Not guarded by lock because it's mostly used to read immutable fields by this conductor.
// This is only modified here at the prepareToStart method which always runs at the vibration
@@ -103,14 +106,15 @@
private int mSuccessfulVibratorOnSteps;
VibrationStepConductor(HalVibration vib, VibrationSettings vibrationSettings,
- DeviceAdapter deviceAdapter,
- VibrationScaler vibrationScaler,
+ DeviceAdapter deviceAdapter, VibrationScaler vibrationScaler,
+ VibratorFrameworkStatsLogger statsLogger,
CompletableFuture<Void> requestVibrationParamsFuture,
VibrationThread.VibratorManagerHooks vibratorManagerHooks) {
this.mVibration = vib;
this.vibrationSettings = vibrationSettings;
this.mDeviceAdapter = deviceAdapter;
mVibrationScaler = vibrationScaler;
+ mStatsLogger = statsLogger;
mRequestVibrationParamsFuture = requestVibrationParamsFuture;
this.vibratorManagerHooks = vibratorManagerHooks;
this.mSignalVibratorsComplete =
@@ -461,9 +465,19 @@
}
try {
- mRequestVibrationParamsFuture.orTimeout(
+ mRequestVibrationParamsFuture.get(
vibrationSettings.getRequestVibrationParamsTimeoutMs(),
- TimeUnit.MILLISECONDS).get();
+ TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ if (DEBUG) {
+ Slog.d(TAG, "Request for vibration params timed out", e);
+ }
+ mStatsLogger.logVibrationParamRequestTimeout(mVibration.callerInfo.uid);
+ } catch (CancellationException e) {
+ if (DEBUG) {
+ Slog.d(TAG, "Request for vibration params cancelled, maybe superseded or"
+ + " vibrator controller unregistered. Skipping params...", e);
+ }
} catch (Throwable e) {
Slog.w(TAG, "Failed to retrieve vibration params.", e);
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
index ec3d99b..10317c9 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControlService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -72,19 +72,21 @@
private final VibrationParamsRecords mVibrationParamsRecords;
private final VibratorControllerHolder mVibratorControllerHolder;
private final VibrationScaler mVibrationScaler;
+ private final VibratorFrameworkStatsLogger mStatsLogger;
private final Object mLock;
private final int[] mRequestVibrationParamsForUsages;
@GuardedBy("mLock")
- private CompletableFuture<Void> mRequestVibrationParamsFuture = null;
- @GuardedBy("mLock")
- private IBinder mRequestVibrationParamsToken;
+ @Nullable
+ private VibrationParamRequest mVibrationParamRequest = null;
VibratorControlService(Context context,
VibratorControllerHolder vibratorControllerHolder, VibrationScaler vibrationScaler,
- VibrationSettings vibrationSettings, Object lock) {
+ VibrationSettings vibrationSettings, VibratorFrameworkStatsLogger statsLogger,
+ Object lock) {
mVibratorControllerHolder = vibratorControllerHolder;
mVibrationScaler = vibrationScaler;
+ mStatsLogger = statsLogger;
mLock = lock;
mRequestVibrationParamsForUsages = vibrationSettings.getRequestVibrationParamsForUsages();
@@ -180,20 +182,25 @@
Objects.requireNonNull(requestToken);
synchronized (mLock) {
- if (mRequestVibrationParamsToken == null) {
+ if (mVibrationParamRequest == null) {
Slog.wtf(TAG,
"New vibration params received but no token was cached in the service. "
+ "New vibration params ignored.");
+ mStatsLogger.logVibrationParamResponseIgnored();
return;
}
- if (!Objects.equals(requestToken, mRequestVibrationParamsToken)) {
+ if (!Objects.equals(requestToken, mVibrationParamRequest.token)) {
Slog.w(TAG,
"New vibration params received but the provided token does not match the "
+ "cached one. New vibration params ignored.");
+ mStatsLogger.logVibrationParamResponseIgnored();
return;
}
+ long latencyMs = SystemClock.uptimeMillis() - mVibrationParamRequest.uptimeMs;
+ mStatsLogger.logVibrationParamRequestLatency(mVibrationParamRequest.uid, latencyMs);
+
updateAdaptiveHapticsScales(result);
endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ false);
recordUpdateVibrationParams(result, /* fromRequest= */ true);
@@ -222,7 +229,7 @@
*/
@Nullable
public CompletableFuture<Void> triggerVibrationParamsRequest(
- @VibrationAttributes.Usage int usage, int timeoutInMillis) {
+ int uid, @VibrationAttributes.Usage int usage, int timeoutInMillis) {
synchronized (mLock) {
IVibratorController vibratorController =
mVibratorControllerHolder.getVibratorController();
@@ -241,16 +248,16 @@
try {
endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ true);
- mRequestVibrationParamsFuture = new CompletableFuture<>();
- mRequestVibrationParamsToken = new Binder();
+ mVibrationParamRequest = new VibrationParamRequest(uid);
vibratorController.requestVibrationParams(vibrationType, timeoutInMillis,
- mRequestVibrationParamsToken);
+ mVibrationParamRequest.token);
+ return mVibrationParamRequest.future;
} catch (RemoteException e) {
Slog.e(TAG, "Failed to request vibration params.", e);
endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ true);
}
- return mRequestVibrationParamsFuture;
+ return null;
}
}
@@ -276,13 +283,13 @@
}
/**
- * Returns the {@link #mRequestVibrationParamsToken} which is used to validate
+ * Returns the binder token which is used to validate
* {@link #onRequestVibrationParamsComplete(IBinder, VibrationParam[])} calls.
*/
@VisibleForTesting
public IBinder getRequestVibrationParamsToken() {
synchronized (mLock) {
- return mRequestVibrationParamsToken;
+ return mVibrationParamRequest == null ? null : mVibrationParamRequest.token;
}
}
@@ -293,7 +300,7 @@
synchronized (mLock) {
isVibratorControllerRegistered =
mVibratorControllerHolder.getVibratorController() != null;
- hasPendingVibrationParamsRequest = mRequestVibrationParamsFuture != null;
+ hasPendingVibrationParamsRequest = mVibrationParamRequest != null;
}
pw.println("VibratorControlService:");
@@ -329,18 +336,10 @@
*/
@GuardedBy("mLock")
private void endOngoingRequestVibrationParamsLocked(boolean wasCancelled) {
- mRequestVibrationParamsToken = null;
- if (mRequestVibrationParamsFuture == null) {
- return;
+ if (mVibrationParamRequest != null) {
+ mVibrationParamRequest.endRequest(wasCancelled);
}
-
- if (wasCancelled) {
- mRequestVibrationParamsFuture.cancel(/* mayInterruptIfRunning= */ true);
- } else {
- mRequestVibrationParamsFuture.complete(null);
- }
-
- mRequestVibrationParamsFuture = null;
+ mVibrationParamRequest = null;
}
private static int mapToAdaptiveVibrationType(@VibrationAttributes.Usage int usage) {
@@ -423,6 +422,7 @@
* @param scale The scaling factor that should be applied to the vibrations.
*/
private void updateAdaptiveHapticsScales(int types, float scale) {
+ mStatsLogger.logVibrationParamScale(scale);
for (int usage : mapFromAdaptiveVibrationTypeToVibrationUsages(types)) {
updateOrRemoveAdaptiveHapticsScale(usage, scale);
}
@@ -504,6 +504,27 @@
}
}
+ /** Represents a request for {@link VibrationParam}. */
+ private static final class VibrationParamRequest {
+ public final CompletableFuture<Void> future = new CompletableFuture<>();
+ public final IBinder token = new Binder();
+ public final int uid;
+ public final long uptimeMs;
+
+ VibrationParamRequest(int uid) {
+ this.uid = uid;
+ uptimeMs = SystemClock.uptimeMillis();
+ }
+
+ public void endRequest(boolean wasCancelled) {
+ if (wasCancelled) {
+ future.cancel(/* mayInterruptIfRunning= */ true);
+ } else {
+ future.complete(null);
+ }
+ }
+ }
+
/**
* Record for a single {@link Vibration.DebugInfo}, that can be grouped by usage and aggregated
* by UID, {@link VibrationAttributes} and {@link VibrationEffect}.
diff --git a/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
index 7e601b6..e9c3894 100644
--- a/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
@@ -25,6 +25,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import com.android.modules.expresslog.Counter;
+import com.android.modules.expresslog.Histogram;
import java.util.ArrayDeque;
import java.util.Queue;
@@ -40,6 +41,23 @@
// Warning about dropping entries after this amount of atoms were dropped by the throttle.
private static final int VIBRATION_REPORTED_WARNING_QUEUE_SIZE = 200;
+ // Latency between 0ms and 99ms, with 100 representing overflow latencies >= 100ms.
+ // Underflow not expected.
+ private static final Histogram sVibrationParamRequestLatencyHistogram = new Histogram(
+ "vibrator.value_vibration_param_request_latency",
+ new Histogram.UniformOptions(20, 0, 100));
+
+ // Scales in [0, 2), with 2 representing overflow scales >= 2.
+ // Underflow expected to represent how many times scales were cleared (set to -1).
+ private static final Histogram sVibrationParamScaleHistogram = new Histogram(
+ "vibrator.value_vibration_param_scale", new Histogram.UniformOptions(20, 0, 2));
+
+ // Scales in [0, 2), with 2 representing overflow scales >= 2.
+ // Underflow not expected.
+ private static final Histogram sAdaptiveHapticScaleHistogram = new Histogram(
+ "vibrator.value_vibration_adaptive_haptic_scale",
+ new Histogram.UniformOptions(20, 0, 2));
+
private final Object mLock = new Object();
private final Handler mHandler;
private final long mVibrationReportedLogIntervalMillis;
@@ -140,6 +158,33 @@
}
}
+ /** Logs adaptive haptic scale value applied to a vibration, only if it's not 1.0. */
+ public void logVibrationAdaptiveHapticScale(int uid, float scale) {
+ if (Float.compare(scale, 1f) != 0) {
+ sAdaptiveHapticScaleHistogram.logSampleWithUid(uid, scale);
+ }
+ }
+
+ /** Logs a vibration param scale value received by the vibrator control service. */
+ public void logVibrationParamScale(float scale) {
+ sVibrationParamScaleHistogram.logSample(scale);
+ }
+
+ /** Logs the latency of a successful vibration params request completed before a vibration. */
+ public void logVibrationParamRequestLatency(int uid, long latencyMs) {
+ sVibrationParamRequestLatencyHistogram.logSampleWithUid(uid, (float) latencyMs);
+ }
+
+ /** Logs a vibration params request timed out before a vibration. */
+ public void logVibrationParamRequestTimeout(int uid) {
+ Counter.logIncrementWithUid("vibrator.value_vibration_param_request_timeout", uid);
+ }
+
+ /** Logs when a response received for a vibration params request is ignored by the service. */
+ public void logVibrationParamResponseIgnored() {
+ Counter.logIncrement("vibrator.value_vibration_param_response_ignored");
+ }
+
/** Logs only if the haptics feedback effect is one of the KEYBOARD_ constants. */
public static void logPerformHapticsFeedbackIfKeyboard(int uid, int hapticsFeedbackEffect) {
boolean isKeyboard;
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index c1bf039..9e9025e 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -212,12 +212,13 @@
mContext = context;
mInjector = injector;
mHandler = injector.createHandler(Looper.myLooper());
+ mFrameworkStatsLogger = injector.getFrameworkStatsLogger(mHandler);
mVibrationSettings = new VibrationSettings(mContext, mHandler);
mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings);
mVibratorControlService = new VibratorControlService(mContext,
injector.createVibratorControllerHolder(), mVibrationScaler, mVibrationSettings,
- mLock);
+ mFrameworkStatsLogger, mLock);
mInputDeviceDelegate = new InputDeviceDelegate(mContext, mHandler);
VibrationCompleteListener listener = new VibrationCompleteListener(this);
@@ -235,7 +236,6 @@
recentDumpSizeLimit, dumpSizeLimit, dumpAggregationTimeLimit);
mBatteryStatsService = injector.getBatteryStatsService();
- mFrameworkStatsLogger = injector.getFrameworkStatsLogger(mHandler);
mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -853,9 +853,7 @@
private void endVibrationLocked(HalVibration vib, Vibration.EndInfo vibrationEndInfo,
boolean shouldWriteStats) {
vib.end(vibrationEndInfo);
- logVibrationStatus(vib.callerInfo.uid, vib.callerInfo.attrs,
- vibrationEndInfo.status);
- mVibratorManagerRecords.record(vib);
+ logAndRecordVibration(vib.getDebugInfo());
if (shouldWriteStats) {
mFrameworkStatsLogger.writeVibrationReportedAsync(
vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
@@ -866,9 +864,7 @@
private void endVibrationAndWriteStatsLocked(ExternalVibrationHolder vib,
Vibration.EndInfo vibrationEndInfo) {
vib.end(vibrationEndInfo);
- logVibrationStatus(vib.externalVibration.getUid(),
- vib.externalVibration.getVibrationAttributes(), vibrationEndInfo.status);
- mVibratorManagerRecords.record(vib);
+ logAndRecordVibration(vib.getDebugInfo());
mFrameworkStatsLogger.writeVibrationReportedAsync(
vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
}
@@ -882,12 +878,12 @@
vib.callerInfo.attrs.getUsage())) {
requestVibrationParamsFuture =
mVibratorControlService.triggerVibrationParamsRequest(
- vib.callerInfo.attrs.getUsage(),
+ vib.callerInfo.uid, vib.callerInfo.attrs.getUsage(),
mVibrationSettings.getRequestVibrationParamsTimeoutMs());
}
return new VibrationStepConductor(vib, mVibrationSettings, mDeviceAdapter, mVibrationScaler,
- requestVibrationParamsFuture, mVibrationThreadCallbacks);
+ mFrameworkStatsLogger, requestVibrationParamsFuture, mVibrationThreadCallbacks);
}
private Vibration.EndInfo startVibrationOnInputDevicesLocked(HalVibration vib) {
@@ -903,6 +899,12 @@
return new Vibration.EndInfo(Vibration.Status.FORWARDED_TO_INPUT_DEVICES);
}
+ private void logAndRecordVibration(Vibration.DebugInfo info) {
+ info.logMetrics(mFrameworkStatsLogger);
+ logVibrationStatus(info.mCallerInfo.uid, info.mCallerInfo.attrs, info.mStatus);
+ mVibratorManagerRecords.record(info);
+ }
+
private void logVibrationStatus(int uid, VibrationAttributes attrs,
Vibration.Status status) {
switch (status) {
@@ -1752,15 +1754,7 @@
new VibrationRecords(recentVibrationSizeLimit, /* aggregationTimeLimit= */ 0);
}
- synchronized void record(HalVibration vib) {
- record(vib.getDebugInfo());
- }
-
- synchronized void record(ExternalVibrationHolder vib) {
- record(vib.getDebugInfo());
- }
-
- private synchronized void record(Vibration.DebugInfo info) {
+ synchronized void record(Vibration.DebugInfo info) {
GroupedAggregatedLogRecords.AggregatedLogRecord<VibrationRecord> droppedRecord =
mRecentVibrations.add(new VibrationRecord(info));
if (droppedRecord != null) {
@@ -2103,6 +2097,7 @@
/** Provide limited functionality from {@link VibratorManagerService} as shell commands. */
private final class VibratorManagerShellCommand extends ShellCommand {
public static final String SHELL_PACKAGE_NAME = "com.android.shell";
+ public static final long VIBRATION_END_TIMEOUT_MS = 500; // Clean up shouldn't be too long.
private final class CommonOptions {
public boolean force = false;
@@ -2478,6 +2473,9 @@
// Waits for the client vibration to finish, but the VibrationThread may still
// do cleanup after this.
vib.waitForEnd();
+ // Wait for vibration clean up and possible ramp down before ending.
+ mVibrationThread.waitForThreadIdle(
+ mVibrationSettings.getRampDownDuration() + VIBRATION_END_TIMEOUT_MS);
} catch (InterruptedException e) {
}
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index 0165d65..65ab129 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -314,6 +314,11 @@
wallpaper.wallpaperId = makeWallpaperIdLocked();
}
+ Rect legacyCropHint = new Rect(
+ getAttributeInt(parser, "cropLeft", 0),
+ getAttributeInt(parser, "cropTop", 0),
+ getAttributeInt(parser, "cropRight", 0),
+ getAttributeInt(parser, "cropBottom", 0));
Rect totalCropHint = new Rect(
getAttributeInt(parser, "totalCropLeft", 0),
getAttributeInt(parser, "totalCropTop", 0),
@@ -332,18 +337,19 @@
parser.getAttributeInt(null, "cropBottom" + pair.second, 0));
if (!cropHint.isEmpty()) wallpaper.mCropHints.put(pair.first, cropHint);
}
- if (wallpaper.mCropHints.size() == 0) {
+ if (wallpaper.mCropHints.size() == 0 && totalCropHint.isEmpty()) {
// migration case: the crops per screen orientation are not specified.
- // use the old attributes to find the crop for one screen orientation.
- Integer orientation = totalCropHint.width() < totalCropHint.height()
+ int orientation = legacyCropHint.width() < legacyCropHint.height()
? WallpaperManager.PORTRAIT : WallpaperManager.LANDSCAPE;
- if (!totalCropHint.isEmpty()) wallpaper.mCropHints.put(orientation, totalCropHint);
+ if (!legacyCropHint.isEmpty()) {
+ wallpaper.mCropHints.put(orientation, legacyCropHint);
+ }
} else {
wallpaper.cropHint.set(totalCropHint);
}
wallpaper.mSampleSize = parser.getAttributeFloat(null, "sampleSize", 1f);
} else {
- wallpaper.cropHint.set(totalCropHint);
+ wallpaper.cropHint.set(legacyCropHint);
}
final DisplayData wpData = mWallpaperDisplayHelper
.getDisplayDataOrCreate(DEFAULT_DISPLAY);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index c3efcb1..885baf6 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2691,6 +2691,7 @@
}
float maxDimAmount = getHighestDimAmountFromMap(wallpaper.mUidToDimAmount);
+ if (wallpaper.mWallpaperDimAmount == maxDimAmount) return;
wallpaper.mWallpaperDimAmount = maxDimAmount;
// Also set the dim amount to the lock screen wallpaper if the lock and home screen
// do not share the same wallpaper
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 78f501a..59a56de 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -1133,10 +1133,12 @@
isIncremental = true;
isLoading = isIncrementalLoading(info.packageName, info.userId);
}
- final boolean stopped = (info.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0;
+ final boolean stopped = wasStoppedNeedsLogging(info);
final int packageState = stopped
? APP_START_OCCURRED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED
: APP_START_OCCURRED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
+
+ final boolean firstLaunch = wasFirstLaunch(info);
FrameworkStatsLog.write(
FrameworkStatsLog.APP_START_OCCURRED,
info.applicationInfo.uid,
@@ -1163,18 +1165,26 @@
TimeUnit.NANOSECONDS.toMillis(info.timestampNs),
processState,
processOomAdj,
- packageState);
+ packageState,
+ false, // is_xr_activity
+ firstLaunch,
+ 0L /* TODO: stoppedDuration */);
+ // Reset the stopped state to avoid reporting stopped again
+ if (info.processRecord != null) {
+ info.processRecord.setWasStoppedLogged(true);
+ }
if (DEBUG_METRICS) {
- Slog.i(TAG, String.format("APP_START_OCCURRED(%s, %s, %s, %s, %s)",
+ Slog.i(TAG, String.format(
+ "APP_START_OCCURRED(%s, %s, %s, %s, %s, wasStopped=%b, firstLaunch=%b)",
info.applicationInfo.uid,
info.packageName,
getAppStartTransitionType(info.type, info.relaunched),
info.launchedActivityName,
- info.launchedActivityLaunchedFromPackage));
+ info.launchedActivityLaunchedFromPackage,
+ stopped, firstLaunch));
}
-
logAppStartMemoryStateCapture(info);
}
@@ -1794,4 +1804,28 @@
return -1;
}
}
+
+ private boolean wasStoppedNeedsLogging(TransitionInfoSnapshot info) {
+ if (info.processRecord != null) {
+ return (info.processRecord.wasForceStopped()
+ || info.processRecord.wasFirstLaunch())
+ && !info.processRecord.getWasStoppedLogged();
+ } else {
+ return (info.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0;
+ }
+ }
+
+ private boolean wasFirstLaunch(TransitionInfoSnapshot info) {
+ if (info.processRecord != null) {
+ return info.processRecord.wasFirstLaunch()
+ && !info.processRecord.getWasStoppedLogged();
+ }
+ try {
+ return !mSupervisor.mService.getPackageManagerInternalLocked()
+ .wasPackageEverLaunched(info.packageName, info.userId);
+ } catch (Exception e) {
+ // Couldn't find the state record, so must be a newly installed app
+ return true;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 7ba953d..6fa6957 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -50,6 +50,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
+import static android.app.WindowConfiguration.isFloating;
import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
@@ -75,6 +76,7 @@
import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
import static android.content.pm.ActivityInfo.FLAG_STATE_NOT_NEEDED;
import static android.content.pm.ActivityInfo.FLAG_TURN_SCREEN_ON;
+import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED;
import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
@@ -332,6 +334,7 @@
import android.service.dreams.DreamActivity;
import android.service.voice.IVoiceInteractionSession;
import android.util.ArraySet;
+import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
import android.util.MergedConfiguration;
@@ -3717,8 +3720,14 @@
final boolean endTask = task.getTopNonFinishingActivity() == null
&& !task.isClearingToReuseTask();
+ final WindowContainer<?> trigger = endTask ? task : this;
final Transition newTransition =
- mTransitionController.requestCloseTransitionIfNeeded(endTask ? task : this);
+ mTransitionController.requestCloseTransitionIfNeeded(trigger);
+ if (newTransition != null) {
+ newTransition.collectClose(trigger);
+ } else if (mTransitionController.isCollecting()) {
+ mTransitionController.getCollectingTransition().collectClose(trigger);
+ }
if (isState(RESUMED)) {
if (endTask) {
mAtmService.getTaskChangeNotificationController().notifyTaskRemovalStarted(
@@ -4377,7 +4386,12 @@
// closing the task.
final WindowContainer trigger = remove && task != null && task.getChildCount() == 1
? task : this;
- mTransitionController.requestCloseTransitionIfNeeded(trigger);
+ final Transition newTransit = mTransitionController.requestCloseTransitionIfNeeded(trigger);
+ if (newTransit != null) {
+ newTransit.collectClose(trigger);
+ } else if (mTransitionController.isCollecting()) {
+ mTransitionController.getCollectingTransition().collectClose(trigger);
+ }
cleanUp(true /* cleanServices */, true /* setState */);
if (remove) {
if (mStartingData != null && mVisible && task != null) {
@@ -7772,8 +7786,11 @@
@Override
void prepareSurfaces() {
- final boolean show = isVisible() || isAnimating(PARENTS,
- ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS
+ final boolean show = (isVisible()
+ // Ensure that the activity content is hidden when the decor surface is boosted to
+ // prevent UI redressing attack.
+ && !getTask().isDecorSurfaceBoosted())
+ || isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS
| ANIMATION_TYPE_PREDICT_BACK);
if (mSurfaceControl != null) {
@@ -8470,6 +8487,9 @@
// and back which can cause visible issues (see b/184078928).
final int parentWindowingMode =
newParentConfiguration.windowConfiguration.getWindowingMode();
+
+ applySizeOverrideIfNeeded(newParentConfiguration, parentWindowingMode, resolvedConfig);
+
final boolean isFixedOrientationLetterboxAllowed =
parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
|| parentWindowingMode == WINDOWING_MODE_FULLSCREEN
@@ -8569,6 +8589,87 @@
}
/**
+ * If necessary, override configuration fields related to app bounds.
+ * This will happen when the app is targeting SDK earlier than 35.
+ * The insets and configuration has decoupled since SDK level 35, to make the system
+ * compatible to existing apps, override the configuration with legacy metrics. In legacy
+ * metrics, fields such as appBounds will exclude some of the system bar areas.
+ * The override contains all potentially affected fields in Configuration, including
+ * screenWidthDp, screenHeightDp, smallestScreenWidthDp, and orientation.
+ * All overrides to those fields should be in this method.
+ */
+ private void applySizeOverrideIfNeeded(Configuration newParentConfiguration,
+ int parentWindowingMode, Configuration inOutConfig) {
+ if (mDisplayContent == null) {
+ return;
+ }
+ final Rect fullBounds = newParentConfiguration.windowConfiguration.getAppBounds();
+ int rotation = newParentConfiguration.windowConfiguration.getRotation();
+ if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) {
+ rotation = mDisplayContent.getRotation();
+ }
+ if (!mWmService.mFlags.mInsetsDecoupledConfiguration
+ || info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)
+ || getCompatDisplayInsets() != null
+ || isFloating(parentWindowingMode) || fullBounds == null
+ || fullBounds.isEmpty() || rotation == ROTATION_UNDEFINED) {
+ // If the insets configuration decoupled logic is not enabled for the app, or the app
+ // already has a compat override, or the context doesn't contain enough info to
+ // calculate the override, skip the override.
+ return;
+ }
+
+ // Override starts here.
+ final Rect stableInsets = mDisplayContent.getDisplayPolicy().getDecorInsetsInfo(
+ rotation, fullBounds.width(), fullBounds.height()).mLegacyConfigInsets;
+ // This should be the only place override the configuration for ActivityRecord. Override
+ // the value if not calculated yet.
+ Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+ if (outAppBounds == null || outAppBounds.isEmpty()) {
+ inOutConfig.windowConfiguration.setAppBounds(fullBounds);
+ outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+ outAppBounds.inset(stableInsets);
+ }
+ float density = inOutConfig.densityDpi;
+ if (density == Configuration.DENSITY_DPI_UNDEFINED) {
+ density = newParentConfiguration.densityDpi;
+ }
+ density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
+ final int overrideScreenWidthDp = (int) (outAppBounds.width() / density + 0.5f);
+ inOutConfig.screenWidthDp =
+ Math.min(overrideScreenWidthDp, newParentConfiguration.screenWidthDp);
+ }
+ if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
+ final int overrideScreenHeightDp =
+ (int) (outAppBounds.height() / density + 0.5f);
+ inOutConfig.screenHeightDp =
+ Math.min(overrideScreenHeightDp, newParentConfiguration.screenHeightDp);
+ }
+ if (inOutConfig.smallestScreenWidthDp
+ == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED
+ && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+ // For the case of PIP transition and multi-window environment, the
+ // smallestScreenWidthDp is handled already. Override only if the app is in
+ // fullscreen.
+ final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+ DisplayInfo info = new DisplayInfo();
+ mDisplayContent.getDisplay().getDisplayInfo(info);
+ mDisplayContent.computeSizeRanges(info, rotated, info.logicalWidth,
+ info.logicalHeight, mDisplayContent.getDisplayMetrics().density,
+ inOutConfig, true /* legacyConfig */);
+ }
+
+ // It's possible that screen size will be considered in different orientation with or
+ // without considering the system bar insets. Override orientation as well.
+ if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
+ inOutConfig.orientation =
+ (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
+ ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
+ }
+ }
+
+ /**
* @return The orientation to use to understand if reachability is enabled.
*/
@Configuration.Orientation
@@ -8821,6 +8922,11 @@
if (mDisplayContent == null) {
return true;
}
+ if (mWmService.mFlags.mInsetsDecoupledConfiguration
+ && info.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)) {
+ // No insets should be considered any more.
+ return true;
+ }
// Only need to make changes if activity sets an orientation
final int requestedOrientation = getRequestedConfigurationOrientation();
if (requestedOrientation == ORIENTATION_UNDEFINED) {
@@ -8835,7 +8941,8 @@
: mDisplayContent.getDisplayInfo();
final Task task = getTask();
task.calculateInsetFrames(mTmpBounds /* outNonDecorBounds */,
- outStableBounds /* outStableBounds */, parentBounds /* bounds */, di);
+ outStableBounds /* outStableBounds */, parentBounds /* bounds */, di,
+ true /* useLegacyInsetsForStableBounds */);
final int orientationWithInsets = outStableBounds.height() >= outStableBounds.width()
? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
// If orientation does not match the orientation with insets applied, then a
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index e283f3e..060f1c8 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3665,7 +3665,7 @@
}
/**
- * Prepare to enter PiP mode after {@link TransitionController#requestStartTransition}.
+ * Prepare to enter PiP mode after {@link TransitionController#requestStartDisplayTransition}.
*
* @param r activity auto entering pip
* @return true if the activity is about to auto-enter pip or is already in pip mode.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 826e332..2fc6b5f 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -57,6 +57,7 @@
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.ActivityRecord.State.STOPPING;
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;
@@ -104,6 +105,7 @@
import android.app.servertransaction.LaunchActivityItem;
import android.app.servertransaction.PauseActivityItem;
import android.app.servertransaction.ResumeActivityItem;
+import android.app.servertransaction.StopActivityItem;
import android.companion.virtual.VirtualDeviceManager;
import android.content.ComponentName;
import android.content.Context;
@@ -944,8 +946,10 @@
if (andResume) {
lifecycleItem = ResumeActivityItem.obtain(r.token, isTransitionForward,
r.shouldSendCompatFakeFocus());
- } else {
+ } else if (r.isVisibleRequested()) {
lifecycleItem = PauseActivityItem.obtain(r.token);
+ } else {
+ lifecycleItem = StopActivityItem.obtain(r.token);
}
// Schedule transaction.
@@ -1013,7 +1017,7 @@
// a resume.
r.setState(RESUMED, "realStartActivityLocked");
r.completeResumeLocked();
- } else {
+ } else if (r.isVisibleRequested()) {
// This activity is not starting in the resumed state... which should look like we asked
// it to pause+stop (but remain visible), and it has done so and reported back the
// current icicle and other state.
@@ -1021,6 +1025,9 @@
+ "(starting in paused state)", r);
r.setState(PAUSED, "realStartActivityLocked");
mRootWindowContainer.executeAppTransitionForAllDisplay();
+ } else {
+ // This activity is starting while invisible, so it should be stopped.
+ r.setState(STOPPING, "realStartActivityLocked");
}
// Perform OOM scoring after the activity state is set, so the process can be updated with
// the latest state.
@@ -1598,9 +1605,14 @@
}
private void removePinnedRootTaskInSurfaceTransaction(Task rootTask) {
- rootTask.mTransitionController.requestTransitionIfNeeded(TRANSIT_TO_BACK, 0 /* flags */,
- rootTask, rootTask.mDisplayContent, null /* remoteTransition */,
- null /* displayChange */);
+ final Transition transition = rootTask.mTransitionController.requestTransitionIfNeeded(
+ TRANSIT_TO_BACK, 0 /* flags */, rootTask, rootTask.mDisplayContent);
+ if (transition == null) {
+ rootTask.mTransitionController.collect(rootTask);
+ } else {
+ transition.collect(rootTask);
+ }
+
/**
* Workaround: Force-stop all the activities in the root pinned task before we reparent them
* to the fullscreen root task. This is to guarantee that when we are removing a root task,
@@ -1683,7 +1695,12 @@
// Prevent recursion.
return;
}
- task.mTransitionController.requestCloseTransitionIfNeeded(task);
+ final Transition transit = task.mTransitionController.requestCloseTransitionIfNeeded(task);
+ if (transit != null) {
+ transit.collectClose(task);
+ } else if (task.mTransitionController.isCollecting()) {
+ task.mTransitionController.getCollectingTransition().collectClose(task);
+ }
// Consume the stopping activities immediately so activity manager won't skip killing
// the process because it is still foreground state, i.e. RESUMED -> PAUSING set from
// removeActivities -> finishIfPossible.
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index c79a8b6..25885ed 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -116,6 +116,7 @@
*/
class SyncGroup {
final int mSyncId;
+ final String mSyncName;
int mSyncMethod = METHOD_BLAST;
final TransactionReadyListener mListener;
final Runnable mOnTimeout;
@@ -138,6 +139,7 @@
private SyncGroup(TransactionReadyListener listener, int id, String name) {
mSyncId = id;
+ mSyncName = name;
mListener = listener;
mOnTimeout = () -> {
Slog.w(TAG, "Sync group " + mSyncId + " timeout");
@@ -221,15 +223,20 @@
for (WindowContainer wc : mRootMembers) {
wc.waitForSyncTransactionCommit(wcAwaitingCommit);
}
+
+ final int syncId = mSyncId;
+ final long mergedTxId = merged.getId();
+ final String syncName = mSyncName;
class CommitCallback implements Runnable {
// Can run a second time if the action completes after the timeout.
boolean ran = false;
public void onCommitted(SurfaceControl.Transaction t) {
+ // Don't wait to hold the global lock to remove the timeout runnable
+ mHandler.removeCallbacks(this);
synchronized (mWm.mGlobalLock) {
if (ran) {
return;
}
- mHandler.removeCallbacks(this);
ran = true;
for (WindowContainer wc : wcAwaitingCommit) {
wc.onSyncTransactionCommitted(t);
@@ -246,8 +253,9 @@
// a trace. Since these kind of ANRs can trigger such an issue,
// try and ensure we will have some visibility in both cases.
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionCommitTimeout");
- Slog.e(TAG, "WM sent Transaction to organized, but never received" +
- " commit callback. Application ANR likely to follow.");
+ Slog.e(TAG, "WM sent Transaction (#" + syncId + ", " + syncName + ", tx="
+ + mergedTxId + ") to organizer, but never received commit callback."
+ + " Application ANR likely to follow.");
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
synchronized (mWm.mGlobalLock) {
mListener.onTransactionCommitTimeout();
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index e155126..e3ac35c 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -60,7 +60,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.server.wm.utils.InsetUtils;
import com.android.window.flags.Flags;
import java.io.PrintWriter;
@@ -1436,15 +1435,11 @@
return null;
}
final WindowState mainWindow = r.findMainWindow();
- Rect insets;
- if (mainWindow != null) {
- insets = mainWindow.getInsetsStateWithVisibilityOverride().calculateInsets(
- mBounds, WindowInsets.Type.tappableElement(),
- false /* ignoreVisibility */).toRect();
- InsetUtils.addInsets(insets, mainWindow.mActivityRecord.getLetterboxInsets());
- } else {
- insets = new Rect();
- }
+ final Rect insets = mainWindow != null
+ ? mainWindow.getInsetsStateWithVisibilityOverride().calculateInsets(
+ mBounds, WindowInsets.Type.tappableElement(),
+ false /* ignoreVisibility */).toRect()
+ : new Rect();
final int mode = mIsOpen ? MODE_OPENING : MODE_CLOSING;
mAnimationTarget = new RemoteAnimationTarget(t.mTaskId, mode, mCapturedLeash,
!r.fillsParent(), new Rect(),
@@ -1686,7 +1681,7 @@
|| (wallpaperController.getWallpaperTarget() != null
&& wallpaperController.wallpaperTransitionReady());
if (wallpaperReady && mPendingAnimation != null) {
- startAnimation();
+ mWindowManagerService.mAnimator.addAfterPrepareSurfacesRunnable(this::startAnimation);
}
}
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 071f403..f7baa79 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -18,10 +18,10 @@
import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityOptions.BackgroundActivityStartMode;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
-import static android.app.ActivityOptions.BackgroundActivityStartMode;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
@@ -37,12 +37,11 @@
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
+import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
import static com.android.window.flags.Flags.balImproveRealCallerVisibilityCheck;
import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator;
import static com.android.window.flags.Flags.balRequireOptInSameUid;
-import static com.android.window.flags.Flags.balShowToasts;
import static com.android.window.flags.Flags.balShowToastsBlocked;
-import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import static java.util.Objects.requireNonNull;
@@ -752,7 +751,6 @@
Slog.wtf(TAG, "With Android 15 BAL hardening this activity start may be blocked"
+ " if the PI creator upgrades target_sdk to 35+! "
+ " (missing opt in by PI creator)!" + state.dump());
- showBalRiskToast();
return allowBasedOnCaller(state);
}
}
@@ -762,7 +760,6 @@
Slog.wtf(TAG, "With Android 14 BAL hardening this activity start will be blocked"
+ " if the PI sender upgrades target_sdk to 34+! "
+ " (missing opt in by PI sender)!" + state.dump());
- showBalRiskToast();
return allowBasedOnRealCaller(state);
}
}
@@ -793,7 +790,11 @@
private BalVerdict abortLaunch(BalState state) {
Slog.wtf(TAG, "Background activity launch blocked! "
+ state.dump());
- showBalBlockedToast();
+ if (balShowToastsBlocked()
+ && (state.mResultForCaller.allows() || state.mResultForRealCaller.allows())) {
+ // only show a toast if either caller or real caller could launch if they opted in
+ showToast("BAL blocked. go/debug-bal");
+ }
return statsLog(BalVerdict.BLOCK, state);
}
@@ -1192,18 +1193,6 @@
return true;
}
- private void showBalBlockedToast() {
- if (balShowToastsBlocked()) {
- showToast("BAL blocked. go/debug-bal");
- }
- }
-
- private void showBalRiskToast() {
- if (balShowToasts()) {
- showToast("BAL allowed in compat mode. go/debug-bal");
- }
- }
-
@VisibleForTesting void showToast(String toastText) {
UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
toastText, Toast.LENGTH_LONG).show());
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index a914c07..b616d24 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -107,7 +107,9 @@
ContentRecorder(@NonNull DisplayContent displayContent) {
this(displayContent, new RemoteMediaProjectionManagerWrapper(displayContent.mDisplayId),
- new DisplayManagerFlags().isConnectedDisplayManagementEnabled());
+ new DisplayManagerFlags().isConnectedDisplayManagementEnabled()
+ && !new DisplayManagerFlags()
+ .isPixelAnisotropyCorrectionInLogicalDisplayEnabled());
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index 7052982..a29cb60 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -113,8 +113,10 @@
// Apply whole display info immediately as is if either:
// * it is the first display update
+ // * the display doesn't have visible content
// * shell transitions are disabled or temporary unavailable
if (displayInfoDiff == DIFF_EVERYTHING
+ || !mDisplayContent.getLastHasContent()
|| !mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
"DeferredDisplayUpdater: applying DisplayInfo immediately");
@@ -171,18 +173,25 @@
mDisplayContent.mInitialDisplayHeight);
final int fromRotation = mDisplayContent.getRotation();
- onStartCollect.run();
+ mDisplayContent.mAtmService.deferWindowLayout();
+ try {
+ onStartCollect.run();
- ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
- "DeferredDisplayUpdater: applied DisplayInfo after deferring");
+ ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
+ "DeferredDisplayUpdater: applied DisplayInfo after deferring");
- if (physicalDisplayUpdated) {
- onDisplayUpdated(transition, fromRotation, startBounds);
- } else {
- final TransitionRequestInfo.DisplayChange displayChange =
- getCurrentDisplayChange(fromRotation, startBounds);
- mDisplayContent.mTransitionController.requestStartTransition(transition,
- /* startTask= */ null, /* remoteTransition= */ null, displayChange);
+ if (physicalDisplayUpdated) {
+ onDisplayUpdated(transition, fromRotation, startBounds);
+ } else {
+ final TransitionRequestInfo.DisplayChange displayChange =
+ getCurrentDisplayChange(fromRotation, startBounds);
+ mDisplayContent.mTransitionController.requestStartTransition(transition,
+ /* startTask= */ null, /* remoteTransition= */ null, displayChange);
+ }
+ } finally {
+ // Run surface placement after requestStartTransition, so shell side can receive
+ // the transition request before handling task info changes.
+ mDisplayContent.mAtmService.continueWindowLayout();
}
});
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index d3acd71..837d08b 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1319,7 +1319,7 @@
for (int i = 0; i < mChildren.size(); i++) {
SurfaceControl sc = mChildren.get(i).getSurfaceControl();
if (sc != null) {
- t.reparent(sc, mSurfaceControl);
+ t.reparent(sc, getParentingSurfaceControl());
}
}
@@ -1625,22 +1625,23 @@
if (configChanged) {
mWaitingForConfig = true;
- if (mTransitionController.isShellTransitionsEnabled()) {
+ if (mLastHasContent && mTransitionController.isShellTransitionsEnabled()) {
final Rect startBounds = currentDisplayConfig.windowConfiguration.getBounds();
final Rect endBounds = mTmpConfiguration.windowConfiguration.getBounds();
- final Transition transition = mTransitionController.getCollectingTransition();
- final TransitionRequestInfo.DisplayChange change = transition != null
- ? null : new TransitionRequestInfo.DisplayChange(mDisplayId);
- if (change != null) {
+ if (!mTransitionController.isCollecting()) {
+ final TransitionRequestInfo.DisplayChange change =
+ new TransitionRequestInfo.DisplayChange(mDisplayId);
change.setStartAbsBounds(startBounds);
change.setEndAbsBounds(endBounds);
+ requestChangeTransition(changes, change);
} else {
+ final Transition transition = mTransitionController.getCollectingTransition();
transition.setKnownConfigChanges(this, changes);
// A collecting transition is existed. The sync method must be set before
// collecting this display, so WindowState#prepareSync can use the sync method.
mTransitionController.setDisplaySyncMethod(startBounds, endBounds, this);
+ collectDisplayChange(transition);
}
- requestChangeTransitionIfNeeded(changes, change);
} else if (mLastHasContent) {
mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */, this);
}
@@ -2301,7 +2302,8 @@
mDisplayInfo.flags &= ~Display.FLAG_SCALING_DISABLED;
}
- computeSizeRanges(mDisplayInfo, rotated, dw, dh, mDisplayMetrics.density, outConfig);
+ computeSizeRanges(mDisplayInfo, rotated, dw, dh, mDisplayMetrics.density, outConfig,
+ false /* legacyConfig */);
setDisplayInfoOverride();
@@ -2437,7 +2439,8 @@
displayInfo.appHeight = appBounds.height();
final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(rotation);
displayInfo.displayCutout = displayCutout.isEmpty() ? null : displayCutout;
- computeSizeRanges(displayInfo, rotated, dw, dh, mDisplayMetrics.density, outConfig);
+ computeSizeRanges(displayInfo, rotated, dw, dh, mDisplayMetrics.density, outConfig,
+ false /* legacyConfig */);
return displayInfo;
}
@@ -2601,8 +2604,21 @@
return curSize;
}
- private void computeSizeRanges(DisplayInfo displayInfo, boolean rotated,
- int dw, int dh, float density, Configuration outConfig) {
+ /**
+ * Compute size range related fields of DisplayInfo and Configuration based on provided info.
+ * The fields usually contain word such as smallest or largest.
+ *
+ * @param displayInfo In-out display info to compute the result.
+ * @param rotated Whether the display is rotated.
+ * @param dw Display Width in current rotation.
+ * @param dh Display Height in current rotation.
+ * @param density Display density.
+ * @param outConfig The output configuration to
+ * @param legacyConfig Whether we need to report the legacy result, which is excluding system
+ * decorations.
+ */
+ void computeSizeRanges(DisplayInfo displayInfo, boolean rotated,
+ int dw, int dh, float density, Configuration outConfig, boolean legacyConfig) {
// We need to determine the smallest width that will occur under normal
// operation. To this, start with the base screen size and compute the
@@ -2620,10 +2636,10 @@
displayInfo.smallestNominalAppHeight = 1<<30;
displayInfo.largestNominalAppWidth = 0;
displayInfo.largestNominalAppHeight = 0;
- adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_0, unrotDw, unrotDh);
- adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_90, unrotDh, unrotDw);
- adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_180, unrotDw, unrotDh);
- adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_270, unrotDh, unrotDw);
+ adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_0, unrotDw, unrotDh, legacyConfig);
+ adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_90, unrotDh, unrotDw, legacyConfig);
+ adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_180, unrotDw, unrotDh, legacyConfig);
+ adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_270, unrotDh, unrotDw, legacyConfig);
if (outConfig == null) {
return;
@@ -2632,11 +2648,19 @@
(int) (displayInfo.smallestNominalAppWidth / density + 0.5f);
}
- private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int rotation, int dw, int dh) {
+ private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int rotation, int dw, int dh,
+ boolean legacyConfig) {
final DisplayPolicy.DecorInsets.Info info = mDisplayPolicy.getDecorInsetsInfo(
rotation, dw, dh);
- final int w = info.mConfigFrame.width();
- final int h = info.mConfigFrame.height();
+ final int w;
+ final int h;
+ if (!legacyConfig) {
+ w = info.mConfigFrame.width();
+ h = info.mConfigFrame.height();
+ } else {
+ w = info.mLegacyConfigFrame.width();
+ h = info.mLegacyConfigFrame.height();
+ }
if (w < displayInfo.smallestNominalAppWidth) {
displayInfo.smallestNominalAppWidth = w;
}
@@ -3551,60 +3575,62 @@
}
/**
- * Requests to start a transition for the display configuration change. The given changes must
- * be non-zero. This method is no-op if the display has been collected.
+ * Collects this display into an already-collecting transition.
*/
- void requestChangeTransitionIfNeeded(@ActivityInfo.Config int changes,
- @Nullable TransitionRequestInfo.DisplayChange displayChange) {
+ void collectDisplayChange(@NonNull Transition transition) {
if (!mLastHasContent) return;
- final TransitionController controller = mTransitionController;
- if (controller.isCollecting()) {
- if (displayChange != null) {
- throw new IllegalArgumentException("Provided displayChange for non-new transition");
- }
- if (!controller.isCollecting(this)) {
- controller.collect(this);
- startAsyncRotationIfNeeded();
- if (mFixedRotationLaunchingApp != null) {
- setSeamlessTransitionForFixedRotation(controller.getCollectingTransition());
- }
- } else if (mAsyncRotationController != null && !isRotationChanging()) {
- Slog.i(TAG, "Finish AsyncRotation for previous intermediate change");
- finishAsyncRotationIfPossible();
- }
- return;
+ if (!transition.isCollecting()) {
+ throw new IllegalArgumentException("Can only collect display change if transition"
+ + " is collecting");
}
- final Transition t = controller.requestTransitionIfNeeded(TRANSIT_CHANGE, 0 /* flags */,
- this, this, null /* remoteTransition */, displayChange);
- if (t != null) {
- mAtmService.startPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
- if (mAsyncRotationController != null) {
- // Give a chance to update the transform if the current rotation is changed when
- // some windows haven't finished previous rotation.
- mAsyncRotationController.updateRotation();
- }
+ if (!transition.mParticipants.contains(this)) {
+ transition.collect(this);
+ startAsyncRotationIfNeeded();
if (mFixedRotationLaunchingApp != null) {
- // A fixed-rotation transition is done, then continue to start a seamless display
- // transition.
- setSeamlessTransitionForFixedRotation(t);
- } else if (isRotationChanging()) {
- if (displayChange != null) {
- final boolean seamless = mDisplayRotation.shouldRotateSeamlessly(
- displayChange.getStartRotation(), displayChange.getEndRotation(),
- false /* forceUpdate */);
- if (seamless) {
- t.onSeamlessRotating(this);
- }
- }
- mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
- controller.mTransitionMetricsReporter.associate(t.getToken(),
- startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN));
- startAsyncRotation(false /* shouldDebounce */);
+ setSeamlessTransitionForFixedRotation(transition);
}
- t.setKnownConfigChanges(this, changes);
+ } else if (mAsyncRotationController != null && !isRotationChanging()) {
+ Slog.i(TAG, "Finish AsyncRotation for previous intermediate change");
+ finishAsyncRotationIfPossible();
}
}
+ /**
+ * Requests to start a transition for a display change. {@code changes} must be non-zero.
+ */
+ void requestChangeTransition(@ActivityInfo.Config int changes,
+ @Nullable TransitionRequestInfo.DisplayChange displayChange) {
+ final TransitionController controller = mTransitionController;
+ final Transition t = controller.requestStartDisplayTransition(TRANSIT_CHANGE, 0 /* flags */,
+ this, null /* remoteTransition */, displayChange);
+ t.collect(this);
+ mAtmService.startPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
+ if (mAsyncRotationController != null) {
+ // Give a chance to update the transform if the current rotation is changed when
+ // some windows haven't finished previous rotation.
+ mAsyncRotationController.updateRotation();
+ }
+ if (mFixedRotationLaunchingApp != null) {
+ // A fixed-rotation transition is done, then continue to start a seamless display
+ // transition.
+ setSeamlessTransitionForFixedRotation(t);
+ } else if (isRotationChanging()) {
+ if (displayChange != null) {
+ final boolean seamless = mDisplayRotation.shouldRotateSeamlessly(
+ displayChange.getStartRotation(), displayChange.getEndRotation(),
+ false /* forceUpdate */);
+ if (seamless) {
+ t.onSeamlessRotating(this);
+ }
+ }
+ mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
+ controller.mTransitionMetricsReporter.associate(t.getToken(),
+ startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN));
+ startAsyncRotation(false /* shouldDebounce */);
+ }
+ t.setKnownConfigChanges(this, changes);
+ }
+
private void setSeamlessTransitionForFixedRotation(Transition t) {
t.setSeamlessRotation(this);
// Before the start transaction is applied, the non-app windows need to keep in previous
@@ -5722,14 +5748,8 @@
*/
void requestTransitionAndLegacyPrepare(@WindowManager.TransitionType int transit,
@WindowManager.TransitionFlags int flags) {
- requestTransitionAndLegacyPrepare(transit, flags, null /* trigger */);
- }
-
- /** @see #requestTransitionAndLegacyPrepare(int, int) */
- void requestTransitionAndLegacyPrepare(@WindowManager.TransitionType int transit,
- @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger) {
prepareAppTransition(transit, flags);
- mTransitionController.requestTransitionIfNeeded(transit, flags, trigger, this);
+ mTransitionController.requestTransitionIfNeeded(transit, flags, null /* trigger */, this);
}
void executeAppTransition() {
@@ -5808,6 +5828,21 @@
|| supportsSystemDecorations();
}
+ /**
+ * Returns the {@link SurfaceControl} where all the children should be parented on.
+ *
+ * <p> {@link DisplayContent} inserts a RootWrapper leash in the hierarchy above its original
+ * {@link #mSurfaceControl} and then overrides the {@link #mSurfaceControl} to point to the
+ * RootWrapper.
+ * <p> To prevent inconsistent state later where the DAs might get re-parented to the
+ * RootWrapper, this method should be used which returns the correct surface where the
+ * re-parenting should happen.
+ */
+ @Override
+ SurfaceControl getParentingSurfaceControl() {
+ return mWindowingLayer;
+ }
+
SurfaceControl getWindowingLayer() {
return mWindowingLayer;
}
@@ -6175,7 +6210,12 @@
* @param onDisplayChangeApplied callback that is called when the changes are applied
*/
void requestDisplayUpdate(@NonNull Runnable onDisplayChangeApplied) {
- mDisplayUpdater.updateDisplayInfo(onDisplayChangeApplied);
+ mAtmService.deferWindowLayout();
+ try {
+ mDisplayUpdater.updateDisplayInfo(onDisplayChangeApplied);
+ } finally {
+ mAtmService.continueWindowLayout();
+ }
}
void onDisplayInfoUpdated(@NonNull DisplayInfo newDisplayInfo) {
@@ -6372,8 +6412,13 @@
if (changes != 0) {
Slog.i(TAG, "Override config changes=" + Integer.toHexString(changes) + " "
+ mTempConfig + " for displayId=" + mDisplayId);
- if (isReady() && mTransitionController.isShellTransitionsEnabled()) {
- requestChangeTransitionIfNeeded(changes, null /* displayChange */);
+ if (isReady() && mTransitionController.isShellTransitionsEnabled() && mLastHasContent) {
+ final Transition transition = mTransitionController.getCollectingTransition();
+ if (transition != null) {
+ collectDisplayChange(transition);
+ } else {
+ requestChangeTransition(changes, null /* displayChange */);
+ }
}
onRequestedOverrideConfigurationChanged(mTempConfig);
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 7f3df95..e789fec 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -25,6 +25,7 @@
import static android.view.InsetsFrameProvider.SOURCE_FRAME;
import static android.view.ViewRootImpl.CLIENT_IMMERSIVE_CONFIRMATION;
import static android.view.ViewRootImpl.CLIENT_TRANSIENT;
+import static android.view.WindowInsetsController.APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
@@ -984,6 +985,9 @@
}
break;
}
+ if ((attrs.insetsFlags.appearance & APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS) != 0) {
+ attrs.insetsFlags.appearance |= APPEARANCE_LIGHT_NAVIGATION_BARS;
+ }
if (LayoutParams.isSystemAlertWindowType(attrs.type)) {
float maxOpacity = mService.mMaximumObscuringOpacityForTouch;
@@ -1913,6 +1917,11 @@
*/
final Rect mConfigInsets = new Rect();
+ /**
+ * Legacy value of mConfigInsets for app compatibility purpose.
+ */
+ final Rect mLegacyConfigInsets = new Rect();
+
/** The display frame available after excluding {@link #mNonDecorInsets}. */
final Rect mNonDecorFrame = new Rect();
@@ -1923,6 +1932,11 @@
*/
final Rect mConfigFrame = new Rect();
+ /**
+ * Legacy value of mConfigFrame for app compatibility purpose.
+ */
+ final Rect mLegacyConfigFrame = new Rect();
+
private boolean mNeedUpdate = true;
InsetsState update(DisplayContent dc, int rotation, int w, int h) {
@@ -1933,15 +1947,26 @@
final Rect displayFrame = insetsState.getDisplayFrame();
final Insets decor = insetsState.calculateInsets(displayFrame,
dc.mWmService.mDecorTypes, true /* ignoreVisibility */);
- final Insets configInsets = insetsState.calculateInsets(displayFrame,
- dc.mWmService.mConfigTypes, true /* ignoreVisibility */);
+ final Insets configInsets = dc.mWmService.mConfigTypes == dc.mWmService.mDecorTypes
+ ? decor
+ : insetsState.calculateInsets(displayFrame, dc.mWmService.mConfigTypes,
+ true /* ignoreVisibility */);
+ final Insets legacyConfigInsets = dc.mWmService.mConfigTypes
+ == dc.mWmService.mLegacyConfigTypes
+ ? configInsets
+ : insetsState.calculateInsets(displayFrame,
+ dc.mWmService.mLegacyConfigTypes, true /* ignoreVisibility */);
mNonDecorInsets.set(decor.left, decor.top, decor.right, decor.bottom);
mConfigInsets.set(configInsets.left, configInsets.top, configInsets.right,
configInsets.bottom);
+ mLegacyConfigInsets.set(legacyConfigInsets.left, legacyConfigInsets.top,
+ legacyConfigInsets.right, legacyConfigInsets.bottom);
mNonDecorFrame.set(displayFrame);
mNonDecorFrame.inset(mNonDecorInsets);
mConfigFrame.set(displayFrame);
mConfigFrame.inset(mConfigInsets);
+ mLegacyConfigFrame.set(displayFrame);
+ mLegacyConfigFrame.inset(mLegacyConfigInsets);
mNeedUpdate = false;
return insetsState;
}
@@ -1949,8 +1974,10 @@
void set(Info other) {
mNonDecorInsets.set(other.mNonDecorInsets);
mConfigInsets.set(other.mConfigInsets);
+ mLegacyConfigInsets.set(other.mLegacyConfigInsets);
mNonDecorFrame.set(other.mNonDecorFrame);
mConfigFrame.set(other.mConfigFrame);
+ mLegacyConfigFrame.set(other.mLegacyConfigFrame);
mNeedUpdate = false;
}
@@ -2062,7 +2089,8 @@
final DecorInsets.Info newInfo = mDecorInsets.mTmpInfo;
final InsetsState newInsetsState = newInfo.update(mDisplayContent, rotation, dw, dh);
final DecorInsets.Info currentInfo = getDecorInsetsInfo(rotation, dw, dh);
- if (newInfo.mConfigFrame.equals(currentInfo.mConfigFrame)) {
+ if (newInfo.mConfigFrame.equals(currentInfo.mConfigFrame)
+ && newInfo.mLegacyConfigFrame.equals(currentInfo.mLegacyConfigFrame)) {
// Even if the config frame is not changed in current rotation, it may change the
// insets in other rotations if the frame of insets source is changed.
final InsetsState currentInsetsState = mDisplayContent.mDisplayFrames.mInsetsState;
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index d376613..384b91a 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -629,13 +629,17 @@
if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
final boolean wasCollecting = mDisplayContent.mTransitionController.isCollecting();
- final TransitionRequestInfo.DisplayChange change = wasCollecting ? null
- : new TransitionRequestInfo.DisplayChange(mDisplayContent.getDisplayId(),
- oldRotation, mRotation);
-
- mDisplayContent.requestChangeTransitionIfNeeded(
- ActivityInfo.CONFIG_WINDOW_CONFIGURATION, change);
- if (wasCollecting) {
+ if (!wasCollecting) {
+ if (mDisplayContent.getLastHasContent()) {
+ final TransitionRequestInfo.DisplayChange change =
+ new TransitionRequestInfo.DisplayChange(mDisplayContent.getDisplayId(),
+ oldRotation, mRotation);
+ mDisplayContent.requestChangeTransition(
+ ActivityInfo.CONFIG_WINDOW_CONFIGURATION, change);
+ }
+ } else {
+ mDisplayContent.collectDisplayChange(
+ mDisplayContent.mTransitionController.getCollectingTransition());
// Use remote-rotation infra since the transition has already been requested
// TODO(shell-transitions): Remove this once lifecycle management can cover all
// rotation cases.
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 9fee343..21326be 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -31,6 +31,7 @@
import android.annotation.Nullable;
import android.graphics.Rect;
import android.os.Trace;
+import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.InsetsSource;
import android.view.InsetsSourceConsumer;
@@ -50,6 +51,8 @@
*/
final class ImeInsetsSourceProvider extends InsetsSourceProvider {
+ private static final String TAG = ImeInsetsSourceProvider.class.getSimpleName();
+
/** The token tracking the current IME request or {@code null} otherwise. */
@Nullable
private ImeTracker.Token mImeRequesterStatsToken;
@@ -220,12 +223,16 @@
*/
void scheduleShowImePostLayout(InsetsControlTarget imeTarget,
@NonNull ImeTracker.Token statsToken) {
+ if (mImeRequesterStatsToken != null) {
+ // Cancel the pre-existing stats token, if any.
+ // Log state on pre-existing request cancel.
+ logShowImePostLayoutState();
+ ImeTracker.forLogging().onCancelled(
+ mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
+ }
+ mImeRequesterStatsToken = statsToken;
boolean targetChanged = isTargetChangedWithinActivity(imeTarget);
mImeRequester = imeTarget;
- // Cancel the pre-existing stats token, if any.
- ImeTracker.forLogging().onCancelled(
- mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
- mImeRequesterStatsToken = statsToken;
if (targetChanged) {
// target changed, check if new target can show IME.
ProtoLog.d(WM_DEBUG_IME, "IME target changed within ActivityRecord");
@@ -297,12 +304,16 @@
*/
void abortShowImePostLayout() {
ProtoLog.d(WM_DEBUG_IME, "abortShowImePostLayout");
+ if (mImeRequesterStatsToken != null) {
+ // Log state on abort.
+ logShowImePostLayoutState();
+ ImeTracker.forLogging().onFailed(
+ mImeRequesterStatsToken, ImeTracker.PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT);
+ mImeRequesterStatsToken = null;
+ }
mImeRequester = null;
mIsImeLayoutDrawn = false;
mShowImeRunner = null;
- ImeTracker.forLogging().onFailed(
- mImeRequesterStatsToken, ImeTracker.PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT);
- mImeRequesterStatsToken = null;
}
@VisibleForTesting
@@ -337,6 +348,41 @@
|| sameAsImeControlTarget();
}
+ /**
+ * Logs the current state required for scheduleShowImePostLayout's runnable to be triggered.
+ */
+ private void logShowImePostLayoutState() {
+ final var windowState = mWindowContainer != null ? mWindowContainer.asWindowState() : null;
+ final var dcTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING);
+ final var controlTarget = mDisplayContent.getImeTarget(IME_TARGET_CONTROL);
+ final var sb = new StringBuilder();
+ sb.append("mWindowContainer: ").append(mWindowContainer);
+ sb.append(" windowState: ").append(windowState);
+ if (windowState != null) {
+ sb.append(" windowState.isDrawn(): ").append(windowState.isDrawn());
+ sb.append(" windowState.mGivenInsetsPending: ").append(windowState.mGivenInsetsPending);
+ }
+ sb.append(" mIsImeLayoutDrawn: ").append(mIsImeLayoutDrawn);
+ sb.append(" mShowImeRunner: ").append(mShowImeRunner);
+ sb.append(" mImeRequester: ").append(mImeRequester);
+ sb.append(" dcTarget: ").append(dcTarget);
+ sb.append(" controlTarget: ").append(controlTarget);
+ sb.append(" isReadyToShowIme(): ").append(isReadyToShowIme());
+ if (mImeRequester != null && dcTarget != null && controlTarget != null) {
+ sb.append(" isImeLayeringTarget: ");
+ sb.append(isImeLayeringTarget(mImeRequester, dcTarget));
+ sb.append(" isAboveImeLayeringTarget: ");
+ sb.append(isAboveImeLayeringTarget(mImeRequester, dcTarget));
+ sb.append(" isImeFallbackTarget: ");
+ sb.append(isImeFallbackTarget(mImeRequester));
+ sb.append(" isImeInputTarget: ");
+ sb.append(isImeInputTarget(mImeRequester));
+ sb.append(" sameAsImeControlTarget: ");
+ sb.append(sameAsImeControlTarget());
+ }
+ Slog.d(TAG, sb.toString());
+ }
+
// ---------------------------------------------------------------------------------------
// Methods for checking IME insets target changing state.
//
@@ -399,6 +445,10 @@
pw.print("showImePostLayout pending for mImeRequester=");
pw.print(mImeRequester);
pw.println();
+ } else {
+ pw.print(prefix);
+ pw.print("showImePostLayout not scheduled, mImeRequester=null");
+ pw.println();
}
pw.println();
}
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 6d11804..b8bb258 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -432,15 +432,22 @@
mService.deferWindowLayout();
try {
if (isKeyguardLocked(displayId)) {
- if (occluded) {
- mRootWindowContainer.getDefaultDisplay().requestTransitionAndLegacyPrepare(
- TRANSIT_KEYGUARD_OCCLUDE,
- TRANSIT_FLAG_KEYGUARD_OCCLUDING,
- topActivity != null ? topActivity.getRootTask() : null);
+ final int type = occluded ? TRANSIT_KEYGUARD_OCCLUDE : TRANSIT_KEYGUARD_UNOCCLUDE;
+ final int flag = occluded ? TRANSIT_FLAG_KEYGUARD_OCCLUDING
+ : TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
+ if (tc.isShellTransitionsEnabled()) {
+ final Task trigger = (occluded && topActivity != null)
+ ? topActivity.getRootTask() : null;
+ Transition transition = tc.requestTransitionIfNeeded(type, flag, trigger,
+ mRootWindowContainer.getDefaultDisplay());
+ if (trigger != null) {
+ if (transition == null) {
+ transition = tc.getCollectingTransition();
+ }
+ transition.collect(trigger);
+ }
} else {
- mRootWindowContainer.getDefaultDisplay().requestTransitionAndLegacyPrepare(
- TRANSIT_KEYGUARD_UNOCCLUDE,
- TRANSIT_FLAG_KEYGUARD_UNOCCLUDING);
+ mRootWindowContainer.getDefaultDisplay().prepareAppTransition(type, flag);
}
} else {
if (tc.inTransition()) {
diff --git a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
index 2b841fd..4c797f8 100644
--- a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
+++ b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
@@ -115,8 +115,8 @@
if (mTransitionController.isCollecting()) {
// Add display container to the currently collecting transition
- mTransitionController.collect(mDisplayContent);
mTransition = mTransitionController.getCollectingTransition();
+ mTransition.collect(mDisplayContent);
// Make sure that transition is not ready until we finish the remote display change
mTransition.setReady(mDisplayContent, false);
@@ -134,10 +134,9 @@
displayChange.setEndAbsBounds(endAbsBounds);
displayChange.setPhysicalDisplayChanged(true);
- mTransition = mTransitionController.requestTransitionIfNeeded(TRANSIT_CHANGE,
- 0 /* flags */,
- mDisplayContent, mDisplayContent, null /* remoteTransition */,
- displayChange);
+ mTransition = mTransitionController.requestStartDisplayTransition(TRANSIT_CHANGE,
+ 0 /* flags */, mDisplayContent, null /* remoteTransition */, displayChange);
+ mTransition.collect(mDisplayContent);
}
if (mTransition != null) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index a75d3b6..445a5c8 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2327,7 +2327,7 @@
}
/**
- * Finish the topmost activities in all root tasks that belong to the crashed app.
+ * Finish the topmost activities in all leaf tasks that belong to the crashed app.
*
* @param app The app that crashed.
* @param reason Reason to perform this action.
@@ -2338,14 +2338,14 @@
Task finishTopCrashedActivities(WindowProcessController app, String reason) {
Task focusedRootTask = getTopDisplayFocusedRootTask();
final Task[] finishedTask = new Task[1];
- forAllRootTasks(rootTask -> {
+ forAllLeafTasks(leafTask -> {
final boolean recordTopOrVisible = finishedTask[0] == null
- && (focusedRootTask == rootTask || rootTask.isVisibleRequested());
- final Task t = rootTask.finishTopCrashedActivityLocked(app, reason);
+ && (focusedRootTask == leafTask.getRootTask() || leafTask.isVisibleRequested());
+ final Task t = leafTask.finishTopCrashedActivityLocked(app, reason);
if (recordTopOrVisible) {
finishedTask[0] = t;
}
- });
+ }, true);
return finishedTask[0];
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 73aa307..55dc30c 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1933,8 +1933,8 @@
td.setEnsureStatusBarContrastWhenTransparent(
atd.getEnsureStatusBarContrastWhenTransparent());
}
- if (td.getStatusBarAppearance() == 0) {
- td.setStatusBarAppearance(atd.getStatusBarAppearance());
+ if (td.getSystemBarsAppearance() == 0) {
+ td.setSystemBarsAppearance(atd.getSystemBarsAppearance());
}
if (td.getNavigationBarColor() == 0) {
td.setNavigationBarColor(atd.getNavigationBarColor());
@@ -3741,7 +3741,9 @@
wc.assignChildLayers(t);
if (!wc.needsZBoost()) {
// Place the decor surface under any untrusted content.
- if (mDecorSurfaceContainer != null && !decorSurfacePlaced
+ if (mDecorSurfaceContainer != null
+ && !mDecorSurfaceContainer.mIsBoosted
+ && !decorSurfacePlaced
&& shouldPlaceDecorSurfaceBelowContainer(wc)) {
mDecorSurfaceContainer.assignLayer(t, layer++);
decorSurfacePlaced = true;
@@ -3760,7 +3762,9 @@
}
// Place the decor surface just above the owner TaskFragment.
- if (mDecorSurfaceContainer != null && !decorSurfacePlaced
+ if (mDecorSurfaceContainer != null
+ && !mDecorSurfaceContainer.mIsBoosted
+ && !decorSurfacePlaced
&& wc == mDecorSurfaceContainer.mOwnerTaskFragment) {
mDecorSurfaceContainer.assignLayer(t, layer++);
decorSurfacePlaced = true;
@@ -3768,10 +3772,10 @@
}
}
- // If not placed yet, the decor surface should be on top of all non-boosted children.
- if (mDecorSurfaceContainer != null && !decorSurfacePlaced) {
+ // Boost the decor surface above other non-boosted windows if requested. The cover surface
+ // will ensure that the content of the windows below are invisible.
+ if (mDecorSurfaceContainer != null && mDecorSurfaceContainer.mIsBoosted) {
mDecorSurfaceContainer.assignLayer(t, layer++);
- decorSurfacePlaced = true;
}
for (int j = 0; j < mChildren.size(); ++j) {
@@ -3796,6 +3800,24 @@
return !isOwnActivity && !isTrustedTaskFragment;
}
+ void setDecorSurfaceBoosted(
+ @NonNull TaskFragment ownerTaskFragment,
+ boolean isBoosted,
+ @Nullable SurfaceControl.Transaction clientTransaction) {
+ if (mDecorSurfaceContainer == null
+ || mDecorSurfaceContainer.mOwnerTaskFragment != ownerTaskFragment) {
+ return;
+ }
+ mDecorSurfaceContainer.setBoosted(isBoosted, clientTransaction);
+ // scheduleAnimation() is called inside assignChildLayers(), which ensures that child
+ // surface visibility is updated with prepareSurfaces()
+ assignChildLayers();
+ }
+
+ boolean isDecorSurfaceBoosted() {
+ return mDecorSurfaceContainer != null && mDecorSurfaceContainer.mIsBoosted;
+ }
+
boolean isTaskId(int taskId) {
return mTaskId == taskId;
}
@@ -6796,14 +6818,35 @@
}
/**
- * A decor surface that is requested by a {@code TaskFragmentOrganizer} which will be placed
- * below children windows except for own Activities and TaskFragment in fully trusted mode.
+ * A class managing the decor surface.
+ *
+ * A decor surface is requested by a {@link TaskFragmentOrganizer} and is placed below children
+ * windows in the Task except for own Activities and TaskFragments in fully trusted mode. The
+ * decor surface is created and shared with the client app with
+ * {@link android.window.TaskFragmentOperation#OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE} and
+ * be removed with
+ * {@link android.window.TaskFragmentOperation#OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE}.
+ *
+ * When boosted with
+ * {@link android.window.TaskFragmentOperation#OP_TYPE_SET_DECOR_SURFACE_BOOSTED}, the decor
+ * surface is placed above all non-boosted windows in the Task, but all the content below it
+ * will be hidden to prevent UI redressing attacks. This can be used by the draggable
+ * divider between {@link TaskFragment}s where veils are drawn on the decor surface while
+ * dragging to indicate new bounds.
*/
@VisibleForTesting
class DecorSurfaceContainer {
+
+ // The container surface is the parent of the decor surface. The container surface
+ // should NEVER be shared with the client. It is used to ensure that the decor surface has
+ // a z-order in the Task that is managed by WM core and cannot be updated by the client
+ // process.
@VisibleForTesting
@NonNull final SurfaceControl mContainerSurface;
+ // The decor surface is shared with the client process owning the
+ // {@link TaskFragmentOrganizer}. It can be used to draw the divider between TaskFragments
+ // or other decorations.
@VisibleForTesting
@NonNull final SurfaceControl mDecorSurface;
@@ -6812,12 +6855,18 @@
@VisibleForTesting
@NonNull TaskFragment mOwnerTaskFragment;
+ private boolean mIsBoosted;
+
+ // The surface transactions that will be applied when the layer is reassigned.
+ @NonNull private final List<SurfaceControl.Transaction> mPendingClientTransactions =
+ new ArrayList<>();
+
private DecorSurfaceContainer(@NonNull TaskFragment initialOwner) {
mOwnerTaskFragment = initialOwner;
mContainerSurface = makeSurface().setContainerLayer()
.setParent(mSurfaceControl)
.setName(mSurfaceControl + " - decor surface container")
- .setEffectLayer()
+ .setContainerLayer()
.setHidden(false)
.setCallsite("Task.DecorSurfaceContainer")
.build();
@@ -6830,14 +6879,28 @@
.build();
}
+ private void setBoosted(
+ boolean isBoosted, @Nullable SurfaceControl.Transaction clientTransaction) {
+ mIsBoosted = isBoosted;
+ // The client transaction will be applied together with the next assignLayer.
+ if (clientTransaction != null) {
+ mDecorSurfaceContainer.mPendingClientTransactions.add(clientTransaction);
+ }
+ }
+
private void assignLayer(@NonNull SurfaceControl.Transaction t, int layer) {
t.setLayer(mContainerSurface, layer);
t.setVisibility(mContainerSurface, mOwnerTaskFragment.isVisible());
+ for (int i = 0; i < mPendingClientTransactions.size(); i++) {
+ t.merge(mPendingClientTransactions.get(i));
+ }
+ mPendingClientTransactions.clear();
}
private void release() {
- mDecorSurface.release();
- mContainerSurface.release();
+ getSyncTransaction()
+ .remove(mDecorSurface)
+ .remove(mContainerSurface);
}
}
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 597e901..3cf561c 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -996,8 +996,7 @@
} else {
// Still have something resumed; can't sleep until it is paused.
ProtoLog.v(WM_DEBUG_STATES, "Sleep needs to pause %s", mResumedActivity);
- startPausing(false /* userLeaving */, true /* uiSleeping */, null /* resuming */,
- "sleep");
+ startPausing(true /* uiSleeping */, null /* resuming */, "sleep");
}
shouldSleep = false;
} else if (mPausingActivity != null) {
@@ -2307,7 +2306,8 @@
// area, i.e. the screen area without the system bars.
// The non decor inset are areas that could never be removed in Honeycomb. See
// {@link WindowManagerPolicy#getNonDecorInsetsLw}.
- calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di);
+ calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di,
+ false /* useLegacyInsetsForStableBounds */);
} else {
// Apply the given non-decor and stable insets to calculate the corresponding bounds
// for screen size of configuration.
@@ -2405,9 +2405,11 @@
* @param outNonDecorBounds where to place bounds with non-decor insets applied.
* @param outStableBounds where to place bounds with stable insets applied.
* @param bounds the bounds to inset.
+ * @param useLegacyInsetsForStableBounds {@code true} if we need to use the legacy insets frame
+ * for apps targeting U or before when calculating stable bounds.
*/
void calculateInsetFrames(Rect outNonDecorBounds, Rect outStableBounds, Rect bounds,
- DisplayInfo displayInfo) {
+ DisplayInfo displayInfo, boolean useLegacyInsetsForStableBounds) {
outNonDecorBounds.set(bounds);
outStableBounds.set(bounds);
if (mDisplayContent == null) {
@@ -2419,7 +2421,11 @@
final DisplayPolicy.DecorInsets.Info info = policy.getDecorInsetsInfo(
displayInfo.rotation, displayInfo.logicalWidth, displayInfo.logicalHeight);
intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, info.mNonDecorInsets);
- intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mConfigInsets);
+ if (!useLegacyInsetsForStableBounds) {
+ intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mConfigInsets);
+ } else {
+ intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mLegacyConfigInsets);
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 7edc3a2..6d8b030 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -503,6 +503,8 @@
}
mConfigAtEndActivities.add(ar);
ar.pauseConfigurationDispatch();
+ snapshotStartState(ar);
+ mChanges.get(ar).mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END;
});
snapshotStartState(wc);
mChanges.get(wc).mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END;
@@ -623,7 +625,8 @@
throw new IllegalStateException("Attempting to re-use a transition");
}
mState = STATE_COLLECTING;
- mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG,
+ mSyncId = mSyncEngine.startSyncSet(this, timeoutMs,
+ TAG + "-" + transitTypeToString(mType),
mParallelCollectType != PARALLEL_TYPE_NONE);
mSyncEngine.setSyncMethod(mSyncId, TransitionController.SYNC_METHOD);
@@ -856,6 +859,19 @@
}
/**
+ * Collects a window container which will be removed or invisible.
+ */
+ void collectClose(@NonNull WindowContainer<?> wc) {
+ if (wc.isVisibleRequested()) {
+ collectExistenceChange(wc);
+ } else {
+ // Removing a non-visible window doesn't require a transition, but if there is one
+ // collecting, this should be a member just in case.
+ collect(wc);
+ }
+ }
+
+ /**
* @return {@code true} if `wc` is a participant or is a descendant of one.
*/
boolean isInTransition(WindowContainer wc) {
@@ -2381,9 +2397,7 @@
} else {
parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_YES_ANIMATION;
}
- final ActivityRecord ar = targetChange.mContainer.asActivityRecord();
- if ((ar != null && ar.isConfigurationDispatchPaused())
- || ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) != 0)) {
+ if ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) != 0) {
parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END;
}
}
@@ -2470,6 +2484,14 @@
} else {
intermediates.add(parentChange);
}
+ // for config-at-end, we want to promote the flag based on the end-state even
+ // if the activity was reparented because it operates after the animation. So,
+ // check that here since the promote code skips reparents.
+ if ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_CONFIG_AT_END) != 0
+ && targetChange.mContainer.asActivityRecord() != null
+ && targetChange.mContainer.getParent() == p) {
+ parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_CONFIG_AT_END;
+ }
foundParentInTargets = true;
break;
} else if (reportIfNotTop(p) && !skipIntermediateReports) {
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 503f925..ac03a1b 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -21,7 +21,6 @@
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_OPEN;
import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
@@ -616,30 +615,6 @@
return changeInfo != null && changeInfo.mRotation != targetRotation;
}
- /**
- * @see #requestTransitionIfNeeded(int, int, WindowContainer, WindowContainer, RemoteTransition)
- */
- @Nullable
- Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type,
- @NonNull WindowContainer trigger) {
- return requestTransitionIfNeeded(type, 0 /* flags */, trigger, trigger /* readyGroupRef */);
- }
-
- /**
- * @see #requestTransitionIfNeeded(int, int, WindowContainer, WindowContainer, RemoteTransition)
- */
- @Nullable
- Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type,
- @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger,
- @NonNull WindowContainer readyGroupRef) {
- return requestTransitionIfNeeded(type, flags, trigger, readyGroupRef,
- null /* remoteTransition */, null /* displayChange */);
- }
-
- private static boolean isExistenceType(@WindowManager.TransitionType int type) {
- return type == TRANSIT_OPEN || type == TRANSIT_CLOSE;
- }
-
/** Sets the sync method for the display change. */
private void setDisplaySyncMethod(@NonNull TransitionRequestInfo.DisplayChange displayChange,
@NonNull DisplayContent displayContent) {
@@ -672,21 +647,17 @@
* start it. Collection can start immediately.
* @param trigger if non-null, this is the first container that will be collected
* @param readyGroupRef Used to identify which ready-group this request is for.
- * @return the created transition if created or null otherwise.
+ * @return the created transition if created or null otherwise (already global collecting)
*/
@Nullable
Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type,
@WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger,
- @NonNull WindowContainer readyGroupRef, @Nullable RemoteTransition remoteTransition,
- @Nullable TransitionRequestInfo.DisplayChange displayChange) {
+ @NonNull WindowContainer readyGroupRef) {
if (mTransitionPlayer == null) {
return null;
}
Transition newTransition = null;
if (isCollecting()) {
- if (displayChange != null) {
- Slog.e(TAG, "Provided displayChange for a non-new request", new Throwable());
- }
// Make the collecting transition wait until this request is ready.
mCollectingTransition.setReady(readyGroupRef, false);
if ((flags & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) {
@@ -695,18 +666,26 @@
}
} else {
newTransition = requestStartTransition(createTransition(type, flags),
- trigger != null ? trigger.asTask() : null, remoteTransition, displayChange);
- if (newTransition != null && displayChange != null && trigger != null
- && trigger.asDisplayContent() != null) {
- setDisplaySyncMethod(displayChange, trigger.asDisplayContent());
- }
+ trigger != null ? trigger.asTask() : null, null /* remote */, null /* disp */);
}
- if (trigger != null) {
- if (isExistenceType(type)) {
- collectExistenceChange(trigger);
- } else {
- collect(trigger);
- }
+ return newTransition;
+ }
+
+ /**
+ * Creates a transition and asks the TransitionPlayer (Shell) to
+ * start it. Collection can start immediately.
+ * @param trigger if non-null, this is the first container that will be collected
+ * @return the created transition if created or null otherwise.
+ */
+ @NonNull
+ Transition requestStartDisplayTransition(@WindowManager.TransitionType int type,
+ @WindowManager.TransitionFlags int flags, @NonNull DisplayContent trigger,
+ @Nullable RemoteTransition remoteTransition,
+ @Nullable TransitionRequestInfo.DisplayChange displayChange) {
+ final Transition newTransition = createTransition(type, flags);
+ requestStartTransition(newTransition, null /* trigger */, remoteTransition, displayChange);
+ if (displayChange != null) {
+ setDisplaySyncMethod(displayChange, trigger);
}
return newTransition;
}
@@ -770,20 +749,10 @@
* @return the new transition if it was created for this request, `null` otherwise.
*/
Transition requestCloseTransitionIfNeeded(@NonNull WindowContainer<?> wc) {
- if (mTransitionPlayer == null) return null;
- Transition out = null;
- if (wc.isVisibleRequested()) {
- if (!isCollecting()) {
- out = requestStartTransition(createTransition(TRANSIT_CLOSE, 0 /* flags */),
- wc.asTask(), null /* remoteTransition */, null /* displayChange */);
- }
- collectExistenceChange(wc);
- } else {
- // Removing a non-visible window doesn't require a transition, but if there is one
- // collecting, this should be a member just in case.
- collect(wc);
- }
- return out;
+ if (mTransitionPlayer == null || isCollecting()) return null;
+ if (!wc.isVisibleRequested()) return null;
+ return requestStartTransition(createTransition(TRANSIT_CLOSE, 0 /* flags */), wc.asTask(),
+ null /* remoteTransition */, null /* displayChange */);
}
/** @see Transition#collect */
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 9b19a70..3fb5998 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -157,13 +157,21 @@
mFindResults.setUseTopWallpaperAsTarget(true);
}
- if (mService.mPolicy.isKeyguardLocked() && w.canShowWhenLocked()) {
- if (mService.mPolicy.isKeyguardOccluded() || (useShellTransition
- ? w.inTransition() : mService.mPolicy.isKeyguardUnoccluding())) {
- // The lowest show when locked window decides whether we need to put the wallpaper
- // behind.
- mFindResults.mNeedsShowWhenLockedWallpaper = !isFullscreen(w.mAttrs)
- || (w.mActivityRecord != null && !w.mActivityRecord.fillsParent());
+ if (mService.mPolicy.isKeyguardLocked()) {
+ if (w.canShowWhenLocked()) {
+ if (mService.mPolicy.isKeyguardOccluded() || (useShellTransition
+ ? w.inTransition() : mService.mPolicy.isKeyguardUnoccluding())) {
+ // The lowest show-when-locked window decides whether to show wallpaper.
+ mFindResults.mNeedsShowWhenLockedWallpaper = !isFullscreen(w.mAttrs)
+ || (w.mActivityRecord != null && !w.mActivityRecord.fillsParent());
+ }
+ } else if (w.hasWallpaper() && mService.mPolicy.isKeyguardHostWindow(w.mAttrs)
+ && w.mTransitionController.isTransitionOnDisplay(mDisplayContent)) {
+ // If we have no candidates at all, notification shade is allowed to be the target
+ // of last resort even if it has not been made visible yet.
+ if (DEBUG_WALLPAPER) Slog.v(TAG, "Found keyguard as wallpaper target: " + w);
+ mFindResults.setWallpaperTarget(w);
+ return false;
}
}
@@ -200,14 +208,7 @@
private boolean isRecentsTransitionTarget(WindowState w) {
if (w.mTransitionController.isShellTransitionsEnabled()) {
- // Because the recents activity is invisible in background while keyguard is occluded
- // (the activity window is on screen while keyguard is locked) with recents animation,
- // the task animating by recents needs to be wallpaper target to make wallpaper visible.
- // While for unlocked case, because recents activity will be moved to top, it can be
- // the wallpaper target naturally.
- return w.mActivityRecord != null && w.mAttrs.type == TYPE_BASE_APPLICATION
- && mDisplayContent.isKeyguardLocked()
- && w.mTransitionController.isTransientHide(w.getTask());
+ return false;
}
// The window is either the recents activity or is in the task animating by the recents.
final RecentsAnimationController controller = mService.getRecentsAnimationController();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index fd0289e..f2af852 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -716,7 +716,7 @@
// If parent is null, the layer should be placed offscreen so reparent to null. Otherwise,
// set to the available parent.
- t.reparent(mSurfaceControl, mParent == null ? null : mParent.getSurfaceControl());
+ t.reparent(mSurfaceControl, mParent == null ? null : mParent.getParentingSurfaceControl());
if (mLastRelativeToLayer != null) {
t.setRelativeLayer(mSurfaceControl, mLastRelativeToLayer, mLastLayer);
@@ -2907,6 +2907,17 @@
}
/**
+ * Returns the {@link SurfaceControl} where all the children should be parented on.
+ *
+ * A {@link WindowContainer} might insert intermediate leashes in the hierarchy and hence
+ * {@link #getSurfaceControl} won't return the correct surface where the children should be
+ * re-parented on.
+ */
+ SurfaceControl getParentingSurfaceControl() {
+ return getSurfaceControl();
+ }
+
+ /**
* Use this method instead of {@link #getPendingTransaction()} if the Transaction should be
* synchronized with the client.
*
diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java
index 7b0d931..294733e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerFlags.java
+++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java
@@ -48,5 +48,7 @@
final boolean mAllowsScreenSizeDecoupledFromStatusBarAndCutout =
Flags.allowsScreenSizeDecoupledFromStatusBarAndCutout();
+ final boolean mInsetsDecoupledConfiguration = Flags.insetsDecoupledConfiguration();
+
/* End Available Flags */
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a055db2..71ffabf 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -561,6 +561,8 @@
/** Device default insets types shall be excluded from config app sizes. */
final int mConfigTypes;
+ final int mLegacyConfigTypes;
+
final boolean mLimitedAlphaCompositing;
final int mMaxUiWidth;
@@ -1190,13 +1192,23 @@
final boolean isScreenSizeDecoupledFromStatusBarAndCutout = context.getResources()
.getBoolean(R.bool.config_decoupleStatusBarAndDisplayCutoutFromScreenSize)
&& mFlags.mAllowsScreenSizeDecoupledFromStatusBarAndCutout;
- if (!isScreenSizeDecoupledFromStatusBarAndCutout) {
+ if (mFlags.mInsetsDecoupledConfiguration) {
+ mDecorTypes = 0;
+ mConfigTypes = 0;
+ } else if (isScreenSizeDecoupledFromStatusBarAndCutout) {
+ mDecorTypes = WindowInsets.Type.navigationBars();
+ mConfigTypes = WindowInsets.Type.navigationBars();
+ } else {
mDecorTypes = WindowInsets.Type.displayCutout() | WindowInsets.Type.navigationBars();
mConfigTypes = WindowInsets.Type.displayCutout() | WindowInsets.Type.statusBars()
| WindowInsets.Type.navigationBars();
+ }
+ if (isScreenSizeDecoupledFromStatusBarAndCutout) {
+ // Do not fallback to legacy value for enabled devices.
+ mLegacyConfigTypes = WindowInsets.Type.navigationBars();
} else {
- mDecorTypes = WindowInsets.Type.navigationBars();
- mConfigTypes = WindowInsets.Type.navigationBars();
+ mLegacyConfigTypes = WindowInsets.Type.displayCutout() | WindowInsets.Type.statusBars()
+ | WindowInsets.Type.navigationBars();
}
mLetterboxConfiguration = new LetterboxConfiguration(
@@ -3646,7 +3658,11 @@
public void setCurrentUser(@UserIdInt int newUserId) {
synchronized (mGlobalLock) {
- mAtmService.getTransitionController().requestTransitionIfNeeded(TRANSIT_OPEN, null);
+ final TransitionController controller = mAtmService.getTransitionController();
+ if (!controller.isCollecting() && controller.isShellTransitionsEnabled()) {
+ controller.requestStartTransition(controller.createTransition(TRANSIT_OPEN),
+ null /* trigger */, null /* remote */, null /* disp */);
+ }
mCurrentUserId = newUserId;
mPolicy.setCurrentUserLw(newUserId);
mKeyguardDisableHandler.setCurrentUser(newUserId);
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index a7eb444..7e6f5ac 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -34,6 +34,7 @@
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_COMPANION_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_DECOR_SURFACE_BOOSTED;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH;
@@ -124,6 +125,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
import com.android.server.pm.LauncherAppsService.LauncherAppsServiceInternal;
+import com.android.window.flags.Flags;
import java.util.ArrayList;
import java.util.HashMap;
@@ -1557,13 +1559,11 @@
break;
}
case OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE: {
- final Task task = taskFragment.getTask();
- task.moveOrCreateDecorSurfaceFor(taskFragment);
+ taskFragment.getTask().moveOrCreateDecorSurfaceFor(taskFragment);
break;
}
case OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE: {
- final Task task = taskFragment.getTask();
- task.removeDecorSurface();
+ taskFragment.getTask().removeDecorSurface();
break;
}
case OP_TYPE_SET_DIM_ON_TASK: {
@@ -1577,6 +1577,23 @@
operation.isMoveToBottomIfClearWhenLaunch());
break;
}
+ case OP_TYPE_SET_DECOR_SURFACE_BOOSTED: {
+ if (Flags.activityEmbeddingInteractiveDividerFlag()) {
+ final SurfaceControl.Transaction clientTransaction =
+ operation.getSurfaceTransaction();
+ if (clientTransaction != null) {
+ // Sanitize the client transaction. sanitize() silently removes invalid
+ // operations and does not throw or provide signal about whether there are
+ // any invalid operations.
+ clientTransaction.sanitize(caller.mPid, caller.mUid);
+ }
+ taskFragment.getTask().setDecorSurfaceBoosted(
+ taskFragment,
+ operation.getBooleanValue() /* isBoosted */,
+ clientTransaction);
+ }
+ break;
+ }
}
return effects;
}
@@ -1616,19 +1633,6 @@
return false;
}
- // TODO (b/293654166) remove the decor surface checks once we clear security reviews
- if ((opType == OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE
- || opType == OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE)
- && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
- final Throwable exception = new SecurityException(
- "Only a system organizer can perform OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE"
- + " or OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE."
- );
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
- opType, exception);
- return false;
- }
-
if ((opType == OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH)
&& !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
final Throwable exception = new SecurityException(
@@ -2018,6 +2022,7 @@
try {
callback.onTransactionReady(syncId, t);
} catch (RemoteException e) {
+ Slog.e(TAG, "Failed to notify transaction (" + syncId + ") ready", e);
// If there's an exception when trying to send the mergedTransaction to the client, we
// should immediately apply it here so the transactions aren't lost.
t.apply();
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index ee16a37..6ac2774 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -114,6 +114,10 @@
private static final long RAPID_ACTIVITY_LAUNCH_MS = 300;
private static final long RESET_RAPID_ACTIVITY_LAUNCH_MS = 5 * RAPID_ACTIVITY_LAUNCH_MS;
+ public static final int STOPPED_STATE_NOT_STOPPED = 0;
+ public static final int STOPPED_STATE_FIRST_LAUNCH = 1;
+ public static final int STOPPED_STATE_FORCE_STOPPED = 2;
+
private int mRapidActivityLaunchCount;
// all about the first app in the process
@@ -281,6 +285,22 @@
@AnimatingReason
private int mAnimatingReasons;
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ STOPPED_STATE_NOT_STOPPED,
+ STOPPED_STATE_FIRST_LAUNCH,
+ STOPPED_STATE_FORCE_STOPPED
+ })
+ public @interface StoppedState {}
+
+ private volatile @StoppedState int mStoppedState;
+
+ /**
+ * Whether the stopped state was logged for an activity start, as we don't want to log
+ * multiple times.
+ */
+ private volatile boolean mWasStoppedLogged;
+
// The bits used for mActivityStateFlags.
private static final int ACTIVITY_STATE_FLAG_IS_VISIBLE = 1 << 16;
private static final int ACTIVITY_STATE_FLAG_IS_PAUSING_OR_PAUSED = 1 << 17;
@@ -1928,6 +1948,29 @@
&& (mInfo.flags & ApplicationInfo.FLAG_FACTORY_TEST) != 0;
}
+ /** Sets the current stopped state of the app, which is reset as soon as metrics are logged */
+ public void setStoppedState(@StoppedState int stoppedState) {
+ mStoppedState = stoppedState;
+ }
+
+ boolean getWasStoppedLogged() {
+ return mWasStoppedLogged;
+ }
+
+ void setWasStoppedLogged(boolean logged) {
+ mWasStoppedLogged = logged;
+ }
+
+ /** Returns whether the app had been force-stopped before this launch */
+ public boolean wasForceStopped() {
+ return mStoppedState == STOPPED_STATE_FORCE_STOPPED;
+ }
+
+ /** Returns whether this app is being launched for the first time since install */
+ boolean wasFirstLaunch() {
+ return mStoppedState == STOPPED_STATE_FIRST_LAUNCH;
+ }
+
void setRunningRecentsAnimation(boolean running) {
if (running) {
addAnimatingReason(ANIMATING_REASON_LEGACY_RECENT_ANIMATION);
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 13e1ba78..4dca23b 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -563,9 +563,15 @@
if (mTransitionController.isShellTransitionsEnabled()
&& asActivityRecord() != null && isVisible()) {
// Trigger an activity level rotation transition.
- mTransitionController.requestTransitionIfNeeded(WindowManager.TRANSIT_CHANGE, this);
- mTransitionController.collectVisibleChange(this);
- mTransitionController.setReady(this);
+ Transition transition = mTransitionController.getCollectingTransition();
+ if (transition == null) {
+ transition = mTransitionController.requestStartTransition(
+ mTransitionController.createTransition(WindowManager.TRANSIT_CHANGE),
+ null /* trigger */, null /* remote */, null /* disp */);
+ }
+ transition.collect(this);
+ transition.collectVisibleChange(this);
+ transition.setReady(mDisplayContent, true);
}
final int originalRotation = getWindowConfiguration().getRotation();
onConfigurationChanged(parent.getConfiguration());
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 3607ddd..7a710dc 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -40,6 +40,7 @@
"com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp",
"com_android_server_ConsumerIrService.cpp",
"com_android_server_companion_virtual_InputController.cpp",
+ "com_android_server_companion_virtual_VirtualDeviceImpl.cpp",
"com_android_server_devicepolicy_CryptoTestHelper.cpp",
"com_android_server_display_DisplayControl.cpp",
"com_android_server_display_SmallAreaDetectionController.cpp",
@@ -214,6 +215,7 @@
static_libs: [
"android.hardware.broadcastradio@common-utils-1x-lib",
"libaidlcommonsupport",
+ "libvirtualdevicebuildflags",
],
product_variables: {
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 4403bce..95e7b19 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -393,6 +393,7 @@
++pageoutVmaIndex;
break;
}
+ return true;
};
meminfo.ForEachVmaFromMaps(vmaCollectorCb, mapsBuffer);
ATRACE_END();
diff --git a/services/core/jni/com_android_server_companion_virtual_VirtualDeviceImpl.cpp b/services/core/jni/com_android_server_companion_virtual_VirtualDeviceImpl.cpp
new file mode 100644
index 0000000..1e6a9db
--- /dev/null
+++ b/services/core/jni/com_android_server_companion_virtual_VirtualDeviceImpl.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android_companion_virtualdevice_build_flags.h>
+#include <nativehelper/JNIHelp.h>
+
+#include <array>
+
+#include "jni.h"
+
+namespace android {
+namespace {
+
+jboolean nativeVirtualCameraServiceBuildFlagEnabled(JNIEnv* env, jobject clazz) {
+ return ::android::companion::virtualdevice::flags::virtual_camera_service_build_flag();
+}
+
+const std::array<JNINativeMethod, 1> kMethods = {
+ {{"nativeVirtualCameraServiceBuildFlagEnabled", "()Z",
+ (void*)nativeVirtualCameraServiceBuildFlagEnabled}},
+};
+
+} // namespace
+
+int register_android_server_companion_virtual_VirtualDeviceImpl(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "com/android/server/companion/virtual/VirtualDeviceImpl",
+ kMethods.data(), kMethods.size());
+}
+
+} // namespace android
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index c778398..610fcb5 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -502,7 +502,7 @@
toString(mLocked.pointerGesturesEnabled));
dump += StringPrintf(INDENT "Show Touches: %s\n", toString(mLocked.showTouches));
dump += StringPrintf(INDENT "Pointer Capture: %s, seq=%" PRIu32 "\n",
- mLocked.pointerCaptureRequest.enable ? "Enabled" : "Disabled",
+ mLocked.pointerCaptureRequest.isEnable() ? "Enabled" : "Disabled",
mLocked.pointerCaptureRequest.seq);
if (auto pc = mLocked.legacyPointerController.lock(); pc) {
dump += pc->dump();
@@ -1717,7 +1717,7 @@
return;
}
- ALOGV("%s pointer capture.", request.enable ? "Enabling" : "Disabling");
+ ALOGV("%s pointer capture.", request.isEnable() ? "Enabling" : "Disabling");
mLocked.pointerCaptureRequest = request;
} // release lock
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 0936888..6464081 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -65,6 +65,7 @@
int register_android_server_stats_pull_StatsPullAtomService(JNIEnv* env);
int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env);
int register_android_server_companion_virtual_InputController(JNIEnv* env);
+int register_android_server_companion_virtual_VirtualDeviceImpl(JNIEnv* env);
int register_android_server_app_GameManagerService(JNIEnv* env);
int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env);
int register_com_android_server_display_DisplayControl(JNIEnv* env);
@@ -128,6 +129,7 @@
register_android_server_stats_pull_StatsPullAtomService(env);
register_android_server_sensor_SensorService(vm, env);
register_android_server_companion_virtual_InputController(env);
+ register_android_server_companion_virtual_VirtualDeviceImpl(env);
register_android_server_app_GameManagerService(env);
register_com_android_server_wm_TaskFpsCallbackController(env);
register_com_android_server_display_DisplayControl(env);
diff --git a/services/core/xsd/device-state-config/device-state-config.xsd b/services/core/xsd/device-state-config/device-state-config.xsd
index 86f4176..4a94732 100644
--- a/services/core/xsd/device-state-config/device-state-config.xsd
+++ b/services/core/xsd/device-state-config/device-state-config.xsd
@@ -40,14 +40,14 @@
<xs:element name="name" type="xs:string" minOccurs="0">
<xs:annotation name="nullable" />
</xs:element>
- <xs:element name="flags" type="flags" />
+ <xs:element name="properties" type="properties" />
<xs:element name="conditions" type="conditions" />
</xs:sequence>
</xs:complexType>
- <xs:complexType name="flags">
+ <xs:complexType name="properties">
<xs:sequence>
- <xs:element name="flag" type="xs:string" minOccurs="0" maxOccurs="unbounded">
+ <xs:element name="property" type="xs:string" minOccurs="0" maxOccurs="unbounded">
<xs:annotation name="nullable" />
</xs:element>
</xs:sequence>
diff --git a/services/core/xsd/device-state-config/schema/current.txt b/services/core/xsd/device-state-config/schema/current.txt
index a98d4e5..5bb216e 100644
--- a/services/core/xsd/device-state-config/schema/current.txt
+++ b/services/core/xsd/device-state-config/schema/current.txt
@@ -11,13 +11,13 @@
public class DeviceState {
ctor public DeviceState();
method public com.android.server.policy.devicestate.config.Conditions getConditions();
- method public com.android.server.policy.devicestate.config.Flags getFlags();
method public java.math.BigInteger getIdentifier();
method @Nullable public String getName();
+ method public com.android.server.policy.devicestate.config.Properties getProperties();
method public void setConditions(com.android.server.policy.devicestate.config.Conditions);
- method public void setFlags(com.android.server.policy.devicestate.config.Flags);
method public void setIdentifier(java.math.BigInteger);
method public void setName(@Nullable String);
+ method public void setProperties(com.android.server.policy.devicestate.config.Properties);
}
public class DeviceStateConfig {
@@ -25,11 +25,6 @@
method public java.util.List<com.android.server.policy.devicestate.config.DeviceState> getDeviceState();
}
- public class Flags {
- ctor public Flags();
- method @Nullable public java.util.List<java.lang.String> getFlag();
- }
-
public class LidSwitchCondition {
ctor public LidSwitchCondition();
method public boolean getOpen();
@@ -48,6 +43,11 @@
method public void setMin_optional(@Nullable java.math.BigDecimal);
}
+ public class Properties {
+ ctor public Properties();
+ method @Nullable public java.util.List<java.lang.String> getProperty();
+ }
+
public class SensorCondition {
ctor public SensorCondition();
method public String getName();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c37946b..ee72db0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3395,7 +3395,9 @@
if (shouldMigrateV1ToDevicePolicyEngine()) {
migrateV1PoliciesToDevicePolicyEngine();
}
+ maybeMigratePoliciesPostUpgradeToDevicePolicyEngineLocked();
migratePoliciesToPolicyEngineLocked();
+
}
maybeStartSecurityLogMonitorOnActivityManagerReady();
break;
@@ -16877,6 +16879,8 @@
private int checkDeviceOwnerProvisioningPreCondition(@UserIdInt int callingUserId) {
synchronized (getLockObject()) {
final int deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode()
+ && (!Flags.headlessDeviceOwnerProvisioningFixEnabled()
+ || getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_AFFILIATED)
? UserHandle.USER_SYSTEM
: callingUserId;
Slogf.i(LOG_TAG, "Calling user %d, device owner will be set on user %d",
@@ -21549,10 +21553,21 @@
setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
setLocale(provisioningParams.getLocale());
+
+
+ boolean isSingleUserMode;
+ if (Flags.headlessDeviceOwnerProvisioningFixEnabled()) {
+ DeviceAdminInfo adminInfo = findAdmin(
+ deviceAdmin, caller.getUserId(), /* throwForMissingPermission= */ false);
+ isSingleUserMode = (adminInfo != null && adminInfo.getHeadlessDeviceOwnerMode()
+ == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER);
+ } else {
+ isSingleUserMode =
+ (getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER);
+ }
int deviceOwnerUserId = Flags.headlessDeviceOwnerSingleUserEnabled()
- && getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER
- ? mUserManagerInternal.getMainUserId()
- : UserHandle.USER_SYSTEM;
+ && isSingleUserMode
+ ? mUserManagerInternal.getMainUserId() : UserHandle.USER_SYSTEM;
if (!removeNonRequiredAppsForManagedDevice(
deviceOwnerUserId,
@@ -23736,7 +23751,9 @@
if (!canForceMigration && !shouldMigrateV1ToDevicePolicyEngine()) {
return false;
}
- return migrateV1PoliciesToDevicePolicyEngine();
+ boolean migrated = migrateV1PoliciesToDevicePolicyEngine();
+ migrated &= migratePoliciesPostUpgradeToDevicePolicyEngineLocked();
+ return migrated;
});
}
@@ -23765,6 +23782,30 @@
/**
* Migrates the initial set of policies to use policy engine.
+ * [b/318497672] Migrate policies that weren't migrated properly in the initial migration on
+ * update from Android T to Android U
+ */
+ private void maybeMigratePoliciesPostUpgradeToDevicePolicyEngineLocked() {
+ if (!mOwners.isMigratedToPolicyEngine() || mOwners.isMigratedPostUpdate()) {
+ return;
+ }
+ migratePoliciesPostUpgradeToDevicePolicyEngineLocked();
+ mOwners.markPostUpgradeMigration();
+ }
+
+ private boolean migratePoliciesPostUpgradeToDevicePolicyEngineLocked() {
+ try {
+ migrateScreenCapturePolicyLocked();
+ migrateLockTaskPolicyLocked();
+ return true;
+ } catch (Exception e) {
+ Slogf.e(LOG_TAG, e, "Error occurred during post upgrade migration to the device "
+ + "policy engine.");
+ return false;
+ }
+ }
+
+ /**
* @return {@code true} if policies were migrated successfully, {@code false} otherwise.
*/
private boolean migrateV1PoliciesToDevicePolicyEngine() {
@@ -23777,7 +23818,6 @@
migrateAutoTimezonePolicy();
migratePermissionGrantStatePolicies();
}
- migrateScreenCapturePolicyLocked();
migratePermittedInputMethodsPolicyLocked();
migrateAccountManagementDisabledPolicyLocked();
migrateUserControlDisabledPackagesLocked();
@@ -23858,14 +23898,12 @@
private void migrateScreenCapturePolicyLocked() {
Binder.withCleanCallingIdentity(() -> {
- if (mPolicyCache.getScreenCaptureDisallowedUser() == UserHandle.USER_NULL) {
- return;
- }
ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
if (admin != null
&& ((isDeviceOwner(admin) && admin.disableScreenCapture)
|| (admin.getParentActiveAdmin() != null
&& admin.getParentActiveAdmin().disableScreenCapture))) {
+
EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
admin.info.getComponent(),
admin.getUserHandle().getIdentifier(),
@@ -23894,6 +23932,48 @@
});
}
+ private void migrateLockTaskPolicyLocked() {
+ Binder.withCleanCallingIdentity(() -> {
+ ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+ if (deviceOwner != null) {
+ int doUserId = deviceOwner.getUserHandle().getIdentifier();
+ DevicePolicyData policies = getUserData(doUserId);
+ List<String> packages = policies.mLockTaskPackages;
+ int features = policies.mLockTaskFeatures;
+ // TODO: find out about persistent preferred activities
+ if (!packages.isEmpty()) {
+ setLockTaskPolicyInPolicyEngine(deviceOwner, doUserId, packages, features);
+ }
+ }
+
+ for (int userId : mUserManagerInternal.getUserIds()) {
+ ActiveAdmin profileOwner = getProfileOwnerLocked(userId);
+ if (profileOwner != null && canDPCManagedUserUseLockTaskLocked(userId)) {
+ DevicePolicyData policies = getUserData(userId);
+ List<String> packages = policies.mLockTaskPackages;
+ int features = policies.mLockTaskFeatures;
+ if (!packages.isEmpty()) {
+ setLockTaskPolicyInPolicyEngine(profileOwner, userId, packages, features);
+ }
+ }
+ }
+ });
+ }
+
+ private void setLockTaskPolicyInPolicyEngine(
+ ActiveAdmin admin, int userId, List<String> packages, int features) {
+ EnforcingAdmin enforcingAdmin =
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ admin.info.getComponent(),
+ userId,
+ admin);
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.LOCK_TASK,
+ enforcingAdmin,
+ new LockTaskPolicy(new HashSet<>(packages), features),
+ userId);
+ }
+
private void migratePermittedInputMethodsPolicyLocked() {
Binder.withCleanCallingIdentity(() -> {
List<UserInfo> users = mUserManager.getUsers();
@@ -24256,4 +24336,13 @@
return mDevicePolicyEngine.getMaxPolicyStorageLimit();
}
+
+ @Override
+ public int getHeadlessDeviceOwnerMode(String callerPackageName) {
+ final CallerIdentity caller = getCallerIdentity(callerPackageName);
+ enforcePermission(MANAGE_PROFILE_AND_DEVICE_OWNERS, caller.getPackageName(),
+ caller.getUserId());
+
+ return Binder.withCleanCallingIdentity(() -> getHeadlessDeviceOwnerMode());
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index c5a9888..7912cbc 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -623,12 +623,25 @@
}
}
+ void markPostUpgradeMigration() {
+ synchronized (mData) {
+ mData.mPoliciesMigratedPostUpdate = true;
+ mData.writeDeviceOwner();
+ }
+ }
+
boolean isSecurityLoggingMigrated() {
synchronized (mData) {
return mData.mSecurityLoggingMigrated;
}
}
+ boolean isMigratedPostUpdate() {
+ synchronized (mData) {
+ return mData.mPoliciesMigratedPostUpdate;
+ }
+ }
+
@GuardedBy("mData")
void pushToAppOpsLocked() {
if (!mSystemReady) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index 9d73ed0..42ac998 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -89,6 +89,8 @@
private static final String ATTR_MIGRATED_TO_POLICY_ENGINE = "migratedToPolicyEngine";
private static final String ATTR_SECURITY_LOG_MIGRATED = "securityLogMigrated";
+ private static final String ATTR_MIGRATED_POST_UPGRADE = "migratedPostUpgrade";
+
// Internal state for the device owner package.
OwnerInfo mDeviceOwner;
int mDeviceOwnerUserId = UserHandle.USER_NULL;
@@ -117,6 +119,8 @@
boolean mMigratedToPolicyEngine = false;
boolean mSecurityLoggingMigrated = false;
+ boolean mPoliciesMigratedPostUpdate = false;
+
OwnersData(PolicyPathProvider pathProvider) {
mPathProvider = pathProvider;
}
@@ -400,6 +404,7 @@
out.startTag(null, TAG_POLICY_ENGINE_MIGRATION);
out.attributeBoolean(null, ATTR_MIGRATED_TO_POLICY_ENGINE, mMigratedToPolicyEngine);
+ out.attributeBoolean(null, ATTR_MIGRATED_POST_UPGRADE, mPoliciesMigratedPostUpdate);
if (Flags.securityLogV2Enabled()) {
out.attributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, mSecurityLoggingMigrated);
}
@@ -463,8 +468,11 @@
case TAG_POLICY_ENGINE_MIGRATION:
mMigratedToPolicyEngine = parser.getAttributeBoolean(
null, ATTR_MIGRATED_TO_POLICY_ENGINE, false);
+ mPoliciesMigratedPostUpdate = parser.getAttributeBoolean(
+ null, ATTR_MIGRATED_POST_UPGRADE, false);
mSecurityLoggingMigrated = Flags.securityLogV2Enabled()
&& parser.getAttributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, false);
+
break;
default:
Slog.e(TAG, "Unexpected tag: " + tag);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 71facab..e713a82 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -506,6 +506,10 @@
UserManager.DISALLOW_SIM_GLOBALLY,
POLICY_FLAG_GLOBAL_ONLY_POLICY);
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ASSIST_CONTENT, /* flags= */ 0);
+ if (com.android.net.thread.platform.flags.Flags.threadUserRestrictionEnabled()) {
+ USER_RESTRICTION_FLAGS.put(
+ UserManager.DISALLOW_THREAD_NETWORK, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+ }
for (String key : USER_RESTRICTION_FLAGS.keySet()) {
createAndAddUserRestrictionPolicyDefinition(key, USER_RESTRICTION_FLAGS.get(key));
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
index 209107e..ce4126a 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
@@ -177,8 +177,11 @@
*/
private static class PostureEstimator implements SensorEventListener, Dumpable {
+ private static final String FLAT_INCLINATION_THRESHOLD_DEGREES_PROPERTY
+ = "persist.foldable_postures.wedge_inclination_threshold_degrees";
- private static final int FLAT_INCLINATION_THRESHOLD_DEGREES = 8;
+ private static final int FLAT_INCLINATION_THRESHOLD_DEGREES = Integer.parseInt(
+ System.getProperty(FLAT_INCLINATION_THRESHOLD_DEGREES_PROPERTY, "25"));
/**
* Alpha parameter of the accelerometer low pass filter: the lower the value, the less high
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
index bc264a4..95c4407 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
@@ -16,29 +16,43 @@
package com.android.server.policy;
-import static android.hardware.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
-import static android.hardware.devicestate.DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP;
-import static android.hardware.devicestate.DeviceState.FLAG_EMULATED_ONLY;
-import static android.hardware.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE;
-import static android.hardware.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL;
+import static android.hardware.devicestate.DeviceState.PROPERTY_EMULATED_ONLY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE;
import static com.android.server.policy.BookStyleStateTransitions.DEFAULT_STATE_TRANSITIONS;
-import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createConfig;
-import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createTentModeClosedState;
+import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStatePredicateWrapper.createConfig;
+import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStatePredicateWrapper.createTentModeClosedState;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceState;
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.FoldableDeviceStateProvider.DeviceStatePredicateWrapper;
import com.android.server.policy.feature.flags.FeatureFlags;
import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
import java.util.function.Predicate;
/**
@@ -55,9 +69,8 @@
private static final int DEVICE_STATE_CLOSED = 0;
private static final int DEVICE_STATE_HALF_OPENED = 1;
private static final int DEVICE_STATE_OPENED = 2;
- private static final int DEVICE_STATE_REAR_DISPLAY_STATE = 3;
+ private static final int DEVICE_STATE_REAR_DISPLAY = 3;
private static final int DEVICE_STATE_CONCURRENT_INNER_DEFAULT = 4;
-
private static final int TENT_MODE_SWITCH_ANGLE_DEGREES = 90;
private static final int TABLE_TOP_MODE_SWITCH_ANGLE_DEGREES = 125;
private static final int MIN_CLOSED_ANGLE_DEGREES = 0;
@@ -92,81 +105,60 @@
mEnablePostureBasedClosedState = featureFlags.enableFoldablesPostureBasedClosedState();
mIsDualDisplayBlockingEnabled = featureFlags.enableDualDisplayBlocking();
- final DeviceStateConfiguration[] configuration = createConfiguration(
+ final DeviceStatePredicateWrapper[] configuration = createConfiguration(
leftAccelerometerSensor, rightAccelerometerSensor, closeAngleDegrees);
- mProvider = new FoldableDeviceStateProvider(mContext, sensorManager,
- hingeAngleSensor, hallSensor, displayManager, configuration);
+ mProvider = new FoldableDeviceStateProvider(mContext, sensorManager, hingeAngleSensor,
+ hallSensor, displayManager, configuration);
}
- private DeviceStateConfiguration[] createConfiguration(@Nullable Sensor leftAccelerometerSensor,
- @Nullable Sensor rightAccelerometerSensor, Integer closeAngleDegrees) {
- return new DeviceStateConfiguration[]{
+ private DeviceStatePredicateWrapper[] createConfiguration(
+ @Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor,
+ Integer closeAngleDegrees) {
+ return new DeviceStatePredicateWrapper[]{
createClosedConfiguration(leftAccelerometerSensor, rightAccelerometerSensor,
closeAngleDegrees),
- createConfig(DEVICE_STATE_HALF_OPENED,
- /* name= */ "HALF_OPENED",
- /* activeStatePredicate= */ (provider) -> {
+ createConfig(getHalfOpenedDeviceState(), /* 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",
- /* activeStatePredicate= */ ALLOWED),
- createConfig(DEVICE_STATE_REAR_DISPLAY_STATE,
- /* name= */ "REAR_DISPLAY_STATE",
- /* flags= */ FLAG_EMULATED_ONLY,
- /* 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,
- /* activeStatePredicate= */ NOT_ALLOWED,
- /* availabilityPredicate= */
+ createConfig(getOpenedDeviceState(), /* activeStatePredicate= */
+ ALLOWED),
+ createConfig(getRearDisplayDeviceState(), /* activeStatePredicate= */
+ NOT_ALLOWED),
+ createConfig(getDualDisplayDeviceState(), /* activeStatePredicate= */
+ NOT_ALLOWED, /* availabilityPredicate= */
provider -> !mIsDualDisplayBlockingEnabled
- || provider.hasNoConnectedExternalDisplay())
- };
+ || provider.hasNoConnectedExternalDisplay())};
}
- private DeviceStateConfiguration createClosedConfiguration(
+ private DeviceStatePredicateWrapper createClosedConfiguration(
@Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor,
@Nullable Integer closeAngleDegrees) {
+
if (closeAngleDegrees != null) {
// Switch displays at closeAngleDegrees in both ways (folding and unfolding)
- return createConfig(
- DEVICE_STATE_CLOSED,
- /* name= */ "CLOSED",
- /* flags= */ FLAG_CANCEL_OVERRIDE_REQUESTS,
- /* activeStatePredicate= */ (provider) -> {
+ return createConfig(getClosedDeviceState(), /* activeStatePredicate= */
+ (provider) -> {
final float hingeAngle = provider.getHingeAngle();
return hingeAngle <= closeAngleDegrees;
- }
- );
+ });
}
if (mEnablePostureBasedClosedState) {
// Use smart closed state predicate that will use different switch angles
// based on the device posture (e.g. wedge mode, tent mode, reverse wedge mode)
- return createConfig(
- DEVICE_STATE_CLOSED,
- /* name= */ "CLOSED",
- /* flags= */ FLAG_CANCEL_OVERRIDE_REQUESTS,
- /* activeStatePredicate= */ new BookStyleClosedStatePredicate(mContext,
- this, leftAccelerometerSensor, rightAccelerometerSensor,
- DEFAULT_STATE_TRANSITIONS)
- );
+ return createConfig(getClosedDeviceState(), /* activeStatePredicate= */
+ new BookStyleClosedStatePredicate(mContext, this, leftAccelerometerSensor,
+ rightAccelerometerSensor, DEFAULT_STATE_TRANSITIONS));
}
// Switch to the outer display only at 0 degrees but use TENT_MODE_SWITCH_ANGLE_DEGREES
// angle when switching to the inner display
- return createTentModeClosedState(DEVICE_STATE_CLOSED,
- /* name= */ "CLOSED",
- /* flags= */ FLAG_CANCEL_OVERRIDE_REQUESTS,
- MIN_CLOSED_ANGLE_DEGREES,
- MAX_CLOSED_ANGLE_DEGREES,
- TENT_MODE_SWITCH_ANGLE_DEGREES);
+ return createTentModeClosedState(getClosedDeviceState(),
+ MIN_CLOSED_ANGLE_DEGREES, MAX_CLOSED_ANGLE_DEGREES, TENT_MODE_SWITCH_ANGLE_DEGREES);
}
@Override
@@ -188,4 +180,84 @@
public void dump(@NonNull PrintWriter writer, @Nullable String[] args) {
mProvider.dump(writer, args);
}
+
+ /** Returns the {@link DeviceState.Configuration} that represents the closed state. */
+ @NonNull
+ private DeviceState getClosedDeviceState() {
+ Set<@DeviceState.SystemDeviceStateProperties Integer> systemProperties = new HashSet<>(
+ List.of(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS,
+ PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY,
+ PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP));
+
+ Set<@DeviceState.PhysicalDeviceStateProperties Integer> physicalProperties = new HashSet<>(
+ List.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED));
+
+ return new DeviceState(new DeviceState.Configuration.Builder(DEVICE_STATE_CLOSED, "CLOSED")
+ .setSystemProperties(systemProperties)
+ .setPhysicalProperties(physicalProperties)
+ .build());
+ }
+
+ /** Returns the {@link DeviceState.Configuration} that represents the half_opened state. */
+ @NonNull
+ private DeviceState getHalfOpenedDeviceState() {
+ Set<@DeviceState.SystemDeviceStateProperties Integer> systemProperties = new HashSet<>(
+ List.of(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY,
+ PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE));
+
+ Set<@DeviceState.PhysicalDeviceStateProperties Integer> physicalProperties = new HashSet<>(
+ List.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN));
+
+ return new DeviceState(new DeviceState.Configuration.Builder(DEVICE_STATE_HALF_OPENED,
+ "HALF_OPENED")
+ .setSystemProperties(systemProperties)
+ .setPhysicalProperties(physicalProperties)
+ .build());
+ }
+
+ /** Returns the {@link DeviceState.Configuration} that represents the opened state */
+ @NonNull
+ private DeviceState getOpenedDeviceState() {
+ Set<@DeviceState.SystemDeviceStateProperties Integer> systemProperties = new HashSet<>(
+ List.of(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY,
+ PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE));
+ Set<@DeviceState.PhysicalDeviceStateProperties Integer> physicalProperties = new HashSet<>(
+ List.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN));
+
+ return new DeviceState(new DeviceState.Configuration.Builder(DEVICE_STATE_OPENED, "OPENED")
+ .setSystemProperties(systemProperties)
+ .setPhysicalProperties(physicalProperties)
+ .build());
+ }
+
+ /** Returns the {@link DeviceState.Configuration} that represents the rear display state. */
+ @NonNull
+ private DeviceState getRearDisplayDeviceState() {
+ Set<@DeviceState.SystemDeviceStateProperties Integer> systemProperties = new HashSet<>(
+ List.of(PROPERTY_EMULATED_ONLY,
+ PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY,
+ PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST, PROPERTY_FEATURE_REAR_DISPLAY));
+
+ return new DeviceState(new DeviceState.Configuration.Builder(DEVICE_STATE_REAR_DISPLAY,
+ "REAR_DISPLAY_STATE")
+ .setSystemProperties(systemProperties)
+ .build());
+ }
+
+ /** Returns the {@link DeviceState.Configuration} that represents the dual display state. */
+ @NonNull
+ private DeviceState getDualDisplayDeviceState() {
+ Set<@DeviceState.SystemDeviceStateProperties Integer> systemProperties = new HashSet<>(
+ List.of(PROPERTY_EMULATED_ONLY, PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP,
+ PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST,
+ PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL,
+ PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE,
+ PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY,
+ PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT));
+
+ return new DeviceState(new DeviceState.Configuration.Builder(
+ DEVICE_STATE_CONCURRENT_INNER_DEFAULT, "CONCURRENT_INNER_DEFAULT")
+ .setSystemProperties(systemProperties)
+ .build());
+ }
}
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 42e41d5..bc8643f 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
@@ -17,13 +17,12 @@
package com.android.server.policy;
import static android.hardware.SensorManager.SENSOR_DELAY_FASTEST;
-import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
-import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE_IDENTIFIER;
-import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE_IDENTIFIER;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.TYPE_EXTERNAL;
-import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
@@ -89,7 +88,7 @@
// the conditions needed for availability.
private final SparseArray<BooleanSupplier> mStateAvailabilityConditions = new SparseArray<>();
- private final DeviceStateConfiguration[] mConfigurations;
+ private final DeviceStatePredicateWrapper[] mConfigurations;
@GuardedBy("mLock")
private final SparseBooleanArray mExternalDisplaysConnected = new SparseBooleanArray();
@@ -103,7 +102,7 @@
@GuardedBy("mLock")
private Listener mListener = null;
@GuardedBy("mLock")
- private int mLastReportedState = INVALID_DEVICE_STATE;
+ private int mLastReportedState = INVALID_DEVICE_STATE_IDENTIFIER;
@GuardedBy("mLock")
private SensorEvent mLastHingeAngleSensorEvent = null;
@GuardedBy("mLock")
@@ -125,9 +124,9 @@
@NonNull Sensor hingeAngleSensor,
@NonNull Sensor hallSensor,
@NonNull DisplayManager displayManager,
- @NonNull DeviceStateConfiguration[] deviceStateConfigurations) {
+ @NonNull DeviceStatePredicateWrapper[] deviceStatePredicateWrappers) {
this(new FeatureFlagsImpl(), context, sensorManager, hingeAngleSensor, hallSensor,
- displayManager, deviceStateConfigurations);
+ displayManager, deviceStatePredicateWrappers);
}
@VisibleForTesting
@@ -138,23 +137,23 @@
@NonNull Sensor hingeAngleSensor,
@NonNull Sensor hallSensor,
@NonNull DisplayManager displayManager,
- @NonNull DeviceStateConfiguration[] deviceStateConfigurations) {
+ @NonNull DeviceStatePredicateWrapper[] deviceStatePredicateWrappers) {
- Preconditions.checkArgument(deviceStateConfigurations.length > 0,
+ Preconditions.checkArgument(deviceStatePredicateWrappers.length > 0,
"Device state configurations array must not be empty");
mHingeAngleSensor = hingeAngleSensor;
mHallSensor = hallSensor;
mDisplayManager = displayManager;
- mConfigurations = deviceStateConfigurations;
+ mConfigurations = deviceStatePredicateWrappers;
mIsDualDisplayBlockingEnabled = featureFlags.enableDualDisplayBlocking();
sensorManager.registerListener(this, mHingeAngleSensor, SENSOR_DELAY_FASTEST);
sensorManager.registerListener(this, mHallSensor, SENSOR_DELAY_FASTEST);
- mOrderedStates = new DeviceState[deviceStateConfigurations.length];
- for (int i = 0; i < deviceStateConfigurations.length; i++) {
- final DeviceStateConfiguration configuration = deviceStateConfigurations[i];
+ mOrderedStates = new DeviceState[deviceStatePredicateWrappers.length];
+ for (int i = 0; i < deviceStatePredicateWrappers.length; i++) {
+ final DeviceStatePredicateWrapper configuration = deviceStatePredicateWrappers[i];
mOrderedStates[i] = configuration.mDeviceState;
assertUniqueDeviceStateIdentifier(configuration);
@@ -174,14 +173,14 @@
// If any of the device states are thermal sensitive, i.e. it should be disabled when
// the device is overheating, then we will update the list of supported states when
// thermal status changes.
- if (hasThermalSensitiveState(deviceStateConfigurations)) {
+ if (hasThermalSensitiveState(deviceStatePredicateWrappers)) {
powerManager.addThermalStatusListener(this);
}
// If any of the device states are power sensitive, i.e. it should be disabled when
// power save mode is enabled, then we will update the list of supported states when
// power save mode is toggled.
- if (hasPowerSaveSensitiveState(deviceStateConfigurations)) {
+ if (hasPowerSaveSensitiveState(deviceStatePredicateWrappers)) {
IntentFilter filter = new IntentFilter(
PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL);
BroadcastReceiver receiver = new BroadcastReceiver() {
@@ -198,7 +197,7 @@
}
}
- private void assertUniqueDeviceStateIdentifier(DeviceStateConfiguration configuration) {
+ private void assertUniqueDeviceStateIdentifier(DeviceStatePredicateWrapper configuration) {
if (mStateConditions.get(configuration.mDeviceState.getIdentifier()) != null) {
throw new IllegalArgumentException("Device state configurations must have unique"
+ " device state identifiers, found duplicated identifier: "
@@ -206,12 +205,12 @@
}
}
- private void initialiseStateConditions(DeviceStateConfiguration configuration) {
+ private void initialiseStateConditions(DeviceStatePredicateWrapper configuration) {
mStateConditions.put(configuration.mDeviceState.getIdentifier(), () ->
configuration.mActiveStatePredicate.test(this));
}
- private void initialiseStateAvailabilityConditions(DeviceStateConfiguration configuration) {
+ private void initialiseStateAvailabilityConditions(DeviceStatePredicateWrapper configuration) {
mStateAvailabilityConditions.put(configuration.mDeviceState.getIdentifier(), () ->
configuration.mAvailabilityPredicate.test(this));
}
@@ -250,13 +249,12 @@
@GuardedBy("mLock")
private boolean isStateSupported(DeviceState deviceState) {
- if (isThermalStatusCriticalOrAbove(mThermalStatus)
- && deviceState.hasFlag(
- DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) {
+ if (isThermalStatusCriticalOrAbove(mThermalStatus) && deviceState.hasProperty(
+ PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) {
return false;
}
- if (mPowerSaveModeEnabled && deviceState.hasFlag(
- DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) {
+ if (mPowerSaveModeEnabled && deviceState.hasProperty(
+ PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) {
return false;
}
if (mIsDualDisplayBlockingEnabled
@@ -270,7 +268,7 @@
/** Computes the current device state and notifies the listener of a change, if needed. */
void notifyDeviceStateChangedIfNeeded() {
- int stateToReport = INVALID_DEVICE_STATE;
+ int stateToReport = INVALID_DEVICE_STATE_IDENTIFIER;
Listener listener;
synchronized (mLock) {
if (mListener == null) {
@@ -279,7 +277,7 @@
listener = mListener;
- int newState = INVALID_DEVICE_STATE;
+ int newState = INVALID_DEVICE_STATE_IDENTIFIER;
for (int i = 0; i < mOrderedStates.length; i++) {
int state = mOrderedStates[i].getIdentifier();
if (DEBUG) {
@@ -307,18 +305,18 @@
break;
}
}
- if (newState == INVALID_DEVICE_STATE) {
+ if (newState == INVALID_DEVICE_STATE_IDENTIFIER) {
Slog.e(TAG, "No declared device states match any of the required conditions.");
dumpSensorValues();
}
- if (newState != INVALID_DEVICE_STATE && newState != mLastReportedState) {
+ if (newState != INVALID_DEVICE_STATE_IDENTIFIER && newState != mLastReportedState) {
mLastReportedState = newState;
stateToReport = newState;
}
}
- if (stateToReport != INVALID_DEVICE_STATE) {
+ if (stateToReport != INVALID_DEVICE_STATE_IDENTIFIER) {
listener.onStateChanged(stateToReport);
}
}
@@ -441,7 +439,7 @@
writer.println(" Predicates:");
for (int i = 0; i < mConfigurations.length; i++) {
- final DeviceStateConfiguration configuration = mConfigurations[i];
+ final DeviceStatePredicateWrapper configuration = mConfigurations[i];
final Predicate<FoldableDeviceStateProvider> predicate =
configuration.mActiveStatePredicate;
@@ -452,22 +450,23 @@
}
/**
- * Configuration for a single device state, contains information about the state like
+ * Configuration wrapper for a single device state, contains information about the state like
* identifier, name, flags and a predicate that should return true if the state should
* be selected.
*/
- public static class DeviceStateConfiguration {
+ public static class DeviceStatePredicateWrapper {
private final DeviceState mDeviceState;
private final Predicate<FoldableDeviceStateProvider> mActiveStatePredicate;
private final Predicate<FoldableDeviceStateProvider> mAvailabilityPredicate;
- private DeviceStateConfiguration(
+ private DeviceStatePredicateWrapper(
@NonNull DeviceState deviceState,
@NonNull Predicate<FoldableDeviceStateProvider> predicate) {
this(deviceState, predicate, ALLOWED);
}
- private DeviceStateConfiguration(
+ /** Create a configuration with availability and availability predicate **/
+ private DeviceStatePredicateWrapper(
@NonNull DeviceState deviceState,
@NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate,
@NonNull Predicate<FoldableDeviceStateProvider> availabilityPredicate) {
@@ -477,38 +476,22 @@
mAvailabilityPredicate = availabilityPredicate;
}
- public static DeviceStateConfiguration createConfig(
- @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to =
- MAXIMUM_DEVICE_STATE_IDENTIFIER) int identifier,
- @NonNull String name,
- @DeviceState.DeviceStateFlags int flags,
+ /** Create a configuration with an active state predicate **/
+ public static DeviceStatePredicateWrapper createConfig(
+ @NonNull DeviceState deviceState,
@NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate
) {
- return new DeviceStateConfiguration(new DeviceState(identifier, name, flags),
- activeStatePredicate);
+ return new DeviceStatePredicateWrapper(deviceState, activeStatePredicate);
}
- public static DeviceStateConfiguration createConfig(
- @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to =
- MAXIMUM_DEVICE_STATE_IDENTIFIER) int identifier,
- @NonNull String name,
- @NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate
- ) {
- return new DeviceStateConfiguration(new DeviceState(identifier, name, /* flags= */ 0),
- activeStatePredicate);
- }
-
- /** Create a configuration with availability predicate **/
- public static DeviceStateConfiguration createConfig(
- @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to =
- MAXIMUM_DEVICE_STATE_IDENTIFIER) int identifier,
- @NonNull String name,
- @DeviceState.DeviceStateFlags int flags,
+ /** Create a configuration with availability and active state predicate **/
+ public static DeviceStatePredicateWrapper createConfig(
+ @NonNull DeviceState deviceState,
@NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate,
@NonNull Predicate<FoldableDeviceStateProvider> availabilityPredicate
) {
- return new DeviceStateConfiguration(new DeviceState(identifier, name, flags),
- activeStatePredicate, availabilityPredicate);
+ return new DeviceStatePredicateWrapper(deviceState, activeStatePredicate,
+ availabilityPredicate);
}
/**
@@ -536,25 +519,20 @@
* so the switch from the inner display to the outer will become only when the device
* is fully closed.
*
- * @param identifier state identifier
- * @param name state name
- * @param flags state flags
+ * @param deviceState {@link DeviceState} that corresponds to this state.
* @param minClosedAngleDegrees minimum (inclusive) hinge angle value for the closed state
* @param maxClosedAngleDegrees maximum (non-inclusive) hinge angle value for the closed
* state
* @param tentModeSwitchAngleDegrees the angle when this state should switch when unfolding
* @return device state configuration
*/
- public static DeviceStateConfiguration createTentModeClosedState(
- @IntRange(from = MINIMUM_DEVICE_STATE_IDENTIFIER, to =
- MAXIMUM_DEVICE_STATE_IDENTIFIER) int identifier,
- @NonNull String name,
- @DeviceState.DeviceStateFlags int flags,
+ public static DeviceStatePredicateWrapper createTentModeClosedState(
+ @NonNull DeviceState deviceState,
int minClosedAngleDegrees,
int maxClosedAngleDegrees,
int tentModeSwitchAngleDegrees
) {
- return new DeviceStateConfiguration(new DeviceState(identifier, name, flags),
+ return new DeviceStatePredicateWrapper(deviceState,
(stateContext) -> {
final boolean hallSensorClosed = stateContext.isHallSensorClosed();
final float hingeAngle = stateContext.getHingeAngle();
@@ -564,9 +542,9 @@
final int switchingDegrees =
isScreenOn ? tentModeSwitchAngleDegrees : maxClosedAngleDegrees;
- final int closedDeviceState = identifier;
+ final int closedDeviceState = deviceState.getIdentifier();
final boolean isLastStateClosed = lastState == closedDeviceState
- || lastState == INVALID_DEVICE_STATE;
+ || lastState == INVALID_DEVICE_STATE_IDENTIFIER;
final boolean shouldBeClosedBecauseTentMode = isLastStateClosed
&& hingeAngle >= minClosedAngleDegrees
@@ -671,21 +649,21 @@
}
}
- private static boolean hasThermalSensitiveState(DeviceStateConfiguration[] deviceStates) {
+ private static boolean hasThermalSensitiveState(DeviceStatePredicateWrapper[] deviceStates) {
for (int i = 0; i < deviceStates.length; i++) {
- DeviceStateConfiguration state = deviceStates[i];
- if (state.mDeviceState
- .hasFlag(DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) {
+ DeviceStatePredicateWrapper state = deviceStates[i];
+ if (state.mDeviceState.hasProperty(
+ PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL)) {
return true;
}
}
return false;
}
- private static boolean hasPowerSaveSensitiveState(DeviceStateConfiguration[] deviceStates) {
+ private static boolean hasPowerSaveSensitiveState(DeviceStatePredicateWrapper[] deviceStates) {
for (int i = 0; i < deviceStates.length; i++) {
- if (deviceStates[i].mDeviceState
- .hasFlag(DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) {
+ if (deviceStates[i].mDeviceState.hasProperty(
+ PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE)) {
return true;
}
}
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 930f4a6..c9bbfee 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
@@ -30,7 +30,7 @@
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 static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createConfig;
+import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStatePredicateWrapper.createConfig;
import static com.google.common.truth.Truth.assertThat;
@@ -59,8 +59,10 @@
import android.testing.AndroidTestingRunner;
import android.view.Display;
+import androidx.annotation.NonNull;
+
import com.android.server.devicestate.DeviceStateProvider.Listener;
-import com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration;
+import com.android.server.policy.FoldableDeviceStateProvider.DeviceStatePredicateWrapper;
import com.android.server.policy.feature.flags.FakeFeatureFlagsImpl;
import com.android.server.policy.feature.flags.Flags;
@@ -73,6 +75,11 @@
import org.mockito.MockitoAnnotations;
import org.mockito.internal.util.reflection.FieldSetter;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
/**
* Unit tests for {@link FoldableDeviceStateProvider}.
* <p/>
@@ -81,8 +88,14 @@
@RunWith(AndroidTestingRunner.class)
public final class FoldableDeviceStateProviderTest {
- private final ArgumentCaptor<DeviceState[]> mDeviceStateArrayCaptor = ArgumentCaptor.forClass(
- DeviceState[].class);
+ private static final Set<Integer> EMPTY_PROPERTY_SET = new HashSet<>();
+ private static final Set<Integer> THERMAL_PROPERTY_SET = new HashSet<>(
+ Arrays.asList(DeviceState.PROPERTY_EMULATED_ONLY,
+ DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL,
+ DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE));
+
+ private final ArgumentCaptor<DeviceState[]> mDeviceStateArrayCaptor =
+ ArgumentCaptor.forClass(DeviceState[].class);
@Captor
private ArgumentCaptor<Integer> mIntegerCaptor;
@Captor
@@ -122,22 +135,17 @@
public void create_duplicatedDeviceStateIdentifiers_throwsException() {
assertThrows(IllegalArgumentException.class,
() -> createProvider(
- createConfig(
- /* identifier= */ 0, /* name= */ "ONE", (c) -> true),
- createConfig(
- /* identifier= */ 0, /* name= */ "TWO", (c) -> true)
+ createConfig(createDeviceState(0, "ONE"), (c) -> true),
+ createConfig(createDeviceState(0, "TWO"), (c) -> true)
));
}
@Test
public void create_allMatchingStatesDefaultsToTheFirstIdentifier() {
createProvider(
- createConfig(
- /* identifier= */ 1, /* name= */ "ONE", (c) -> true),
- createConfig(
- /* identifier= */ 2, /* name= */ "TWO", (c) -> true),
- createConfig(
- /* identifier= */ 3, /* name= */ "THREE", (c) -> true)
+ createConfig(createDeviceState(1, "ONE"), (c) -> true),
+ createConfig(createDeviceState(2, "TWO"), (c) -> true),
+ createConfig(createDeviceState(3, "THREE"), (c) -> true)
);
Listener listener = mock(Listener.class);
@@ -146,9 +154,9 @@
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
final DeviceState[] expectedStates = new DeviceState[]{
- new DeviceState(1, "ONE", /* flags= */ 0),
- new DeviceState(2, "TWO", /* flags= */ 0),
- new DeviceState(3, "THREE", /* flags= */ 0),
+ createDeviceState(1, "ONE", EMPTY_PROPERTY_SET),
+ createDeviceState(2, "TWO", EMPTY_PROPERTY_SET),
+ createDeviceState(3, "THREE", EMPTY_PROPERTY_SET)
};
assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue());
@@ -159,14 +167,10 @@
@Test
public void create_multipleMatchingStatesDefaultsToTheLowestIdentifier() {
createProvider(
- createConfig(
- /* identifier= */ 1, /* name= */ "ONE", (c) -> false),
- createConfig(
- /* identifier= */ 3, /* name= */ "THREE", (c) -> false),
- createConfig(
- /* identifier= */ 4, /* name= */ "FOUR", (c) -> true),
- createConfig(
- /* identifier= */ 2, /* name= */ "TWO", (c) -> true)
+ createConfig(createDeviceState(1, "ONE"), (c) -> false),
+ createConfig(createDeviceState(3, "THREE"), (c) -> false),
+ createConfig(createDeviceState(4, "FOUR"), (c) -> true),
+ createConfig(createDeviceState(2, "TWO"), (c) -> true)
);
Listener listener = mock(Listener.class);
@@ -178,9 +182,9 @@
@Test
public void test_hingeAngleUpdatedFirstTime_switchesToMatchingState() throws Exception {
- createProvider(createConfig(/* identifier= */ 1, /* name= */ "ONE",
+ createProvider(createConfig(createDeviceState(1, "ONE"),
(c) -> c.getHingeAngle() < 90f),
- createConfig(/* identifier= */ 2, /* name= */ "TWO",
+ createConfig(createDeviceState(2, "TWO"),
(c) -> c.getHingeAngle() >= 90f));
Listener listener = mock(Listener.class);
mProvider.setListener(listener);
@@ -195,9 +199,9 @@
@Test
public void test_hallSensorUpdatedFirstTime_switchesToMatchingState() throws Exception {
- createProvider(createConfig(/* identifier= */ 1, /* name= */ "ONE",
+ createProvider(createConfig(createDeviceState(1, "ONE"),
(c) -> !c.isHallSensorClosed()),
- createConfig(/* identifier= */ 2, /* name= */ "TWO",
+ createConfig(createDeviceState(2, "TWO"),
FoldableDeviceStateProvider::isHallSensorClosed));
Listener listener = mock(Listener.class);
mProvider.setListener(listener);
@@ -213,9 +217,9 @@
@Test
public void test_hingeAngleUpdatedSecondTime_switchesToMatchingState() throws Exception {
- createProvider(createConfig(/* identifier= */ 1, /* name= */ "ONE",
+ createProvider(createConfig(createDeviceState(1, "ONE"),
(c) -> c.getHingeAngle() < 90f),
- createConfig(/* identifier= */ 2, /* name= */ "TWO",
+ createConfig(createDeviceState(2, "TWO"),
(c) -> c.getHingeAngle() >= 90f));
sendSensorEvent(mHingeAngleSensor, /* value= */ 30f);
Listener listener = mock(Listener.class);
@@ -232,9 +236,9 @@
@Test
public void test_hallSensorUpdatedSecondTime_switchesToMatchingState() throws Exception {
- createProvider(createConfig(/* identifier= */ 1, /* name= */ "ONE",
+ createProvider(createConfig(createDeviceState(1, "ONE"),
(c) -> !c.isHallSensorClosed()),
- createConfig(/* identifier= */ 2, /* name= */ "TWO",
+ createConfig(createDeviceState(2, "TWO"),
FoldableDeviceStateProvider::isHallSensorClosed));
sendSensorEvent(mHallSensor, /* value= */ 0f);
Listener listener = mock(Listener.class);
@@ -252,9 +256,9 @@
@Test
public void test_invalidSensorValues_onStateChangedIsNotTriggered() throws Exception {
- createProvider(createConfig(/* identifier= */ 1, /* name= */ "ONE",
+ createProvider(createConfig(createDeviceState(1, "ONE"),
(c) -> c.getHingeAngle() < 90f),
- createConfig(/* identifier= */ 2, /* name= */ "TWO",
+ createConfig(createDeviceState(2, "TWO"),
(c) -> c.getHingeAngle() >= 90f));
Listener listener = mock(Listener.class);
mProvider.setListener(listener);
@@ -277,23 +281,21 @@
@Test
public void test_nullSensorValues_noExceptionThrown() throws Exception {
- createProvider(createConfig( /* identifier= */ 1, /* name= */ "ONE",
- (c) -> c.getHingeAngle() < 90f));
+ createProvider(createConfig(createDeviceState(1, "ONE"),
+ (c) -> c.getHingeAngle() < 90f));
sendInvalidSensorEvent(null);
}
@Test
public void test_flagDisableWhenThermalStatusCritical() throws Exception {
- createProvider(createConfig(/* identifier= */ 1, /* name= */ "CLOSED",
+ createProvider(createConfig(createDeviceState(1, "CLOSED"),
(c) -> c.getHingeAngle() < 5f),
- createConfig(/* identifier= */ 2, /* name= */ "HALF_OPENED",
+ createConfig(createDeviceState(2, "HALF_OPENED"),
(c) -> c.getHingeAngle() < 90f),
- createConfig(/* identifier= */ 3, /* name= */ "OPENED",
+ createConfig(createDeviceState(3, "OPENED"),
(c) -> c.getHingeAngle() < 180f),
- createConfig(/* identifier= */ 4, /* name= */ "THERMAL_TEST",
- DeviceState.FLAG_EMULATED_ONLY
- | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
- | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE,
+ createConfig(
+ createDeviceState(4, "THERMAL_TEST", THERMAL_PROPERTY_SET),
(c) -> true));
Listener listener = mock(Listener.class);
mProvider.setListener(listener);
@@ -301,13 +303,10 @@
eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
assertArrayEquals(
new DeviceState[]{
- new DeviceState(1, "CLOSED", 0 /* flags */),
- new DeviceState(2, "HALF_OPENED", 0 /* flags */),
- new DeviceState(3, "OPENED", 0 /* flags */),
- new DeviceState(4, "THERMAL_TEST",
- DeviceState.FLAG_EMULATED_ONLY
- | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
- | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)},
+ createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+ createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(4, "THERMAL_TEST", THERMAL_PROPERTY_SET)},
mDeviceStateArrayCaptor.getValue());
clearInvocations(listener);
@@ -322,9 +321,9 @@
eq(SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL));
assertArrayEquals(
new DeviceState[]{
- new DeviceState(1, "CLOSED", 0 /* flags */),
- new DeviceState(2, "HALF_OPENED", 0 /* flags */),
- new DeviceState(3, "OPENED", 0 /* flags */)},
+ createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+ createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET)},
mDeviceStateArrayCaptor.getValue());
clearInvocations(listener);
@@ -334,28 +333,23 @@
eq(SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL));
assertArrayEquals(
new DeviceState[]{
- new DeviceState(1, "CLOSED", 0 /* flags */),
- new DeviceState(2, "HALF_OPENED", 0 /* flags */),
- new DeviceState(3, "OPENED", 0 /* flags */),
- new DeviceState(4, "THERMAL_TEST",
- DeviceState.FLAG_EMULATED_ONLY
- | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
- | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE)},
+ createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+ createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(4, "THERMAL_TEST", THERMAL_PROPERTY_SET)},
mDeviceStateArrayCaptor.getValue());
}
@Test
public void test_flagDisableWhenPowerSaveEnabled() throws Exception {
- createProvider(createConfig(/* identifier= */ 1, /* name= */ "CLOSED",
+ createProvider(createConfig(createDeviceState(1, "CLOSED"),
(c) -> c.getHingeAngle() < 5f),
- createConfig(/* identifier= */ 2, /* name= */ "HALF_OPENED",
+ createConfig(createDeviceState(2, "HALF_OPENED"),
(c) -> c.getHingeAngle() < 90f),
- createConfig(/* identifier= */ 3, /* name= */ "OPENED",
+ createConfig(createDeviceState(3, "OPENED"),
(c) -> c.getHingeAngle() < 180f),
- createConfig(/* identifier= */ 4, /* name= */ "THERMAL_TEST",
- DeviceState.FLAG_EMULATED_ONLY
- | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
- | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE,
+ createConfig(
+ createDeviceState(4, "THERMAL_TEST", THERMAL_PROPERTY_SET),
(c) -> true));
mProvider.onPowerSaveModeChanged(false /* isPowerSaveModeEnabled */);
Listener listener = mock(Listener.class);
@@ -365,13 +359,10 @@
eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
assertArrayEquals(
new DeviceState[]{
- new DeviceState(1, "CLOSED", 0 /* flags */),
- new DeviceState(2, "HALF_OPENED", 0 /* flags */),
- new DeviceState(3, "OPENED", 0 /* flags */),
- new DeviceState(4, "THERMAL_TEST",
- DeviceState.FLAG_EMULATED_ONLY
- | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
- | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) },
+ createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+ createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(4, "THERMAL_TEST", THERMAL_PROPERTY_SET)},
mDeviceStateArrayCaptor.getValue());
clearInvocations(listener);
@@ -386,9 +377,9 @@
eq(SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED));
assertArrayEquals(
new DeviceState[]{
- new DeviceState(1, "CLOSED", 0 /* flags */),
- new DeviceState(2, "HALF_OPENED", 0 /* flags */),
- new DeviceState(3, "OPENED", 0 /* flags */) },
+ createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+ createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET)},
mDeviceStateArrayCaptor.getValue());
clearInvocations(listener);
@@ -398,13 +389,10 @@
eq(SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED));
assertArrayEquals(
new DeviceState[]{
- new DeviceState(1, "CLOSED", 0 /* flags */),
- new DeviceState(2, "HALF_OPENED", 0 /* flags */),
- new DeviceState(3, "OPENED", 0 /* flags */),
- new DeviceState(4, "THERMAL_TEST",
- DeviceState.FLAG_EMULATED_ONLY
- | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
- | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) },
+ createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+ createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(4, "THERMAL_TEST", THERMAL_PROPERTY_SET)},
mDeviceStateArrayCaptor.getValue());
}
@@ -413,13 +401,11 @@
// Create a configuration where state TWO could be matched only if
// the previous state was 'THREE'
createProvider(
- createConfig(
- /* identifier= */ 1, /* name= */ "ONE", (c) -> c.getHingeAngle() < 30f),
- createConfig(
- /* identifier= */ 2, /* name= */ "TWO",
+ createConfig(createDeviceState(1, "ONE"),
+ (c) -> c.getHingeAngle() < 30f),
+ createConfig(createDeviceState(2, "TWO"),
(c) -> c.getLastReportedDeviceState() == 3 && c.getHingeAngle() > 120f),
- createConfig(
- /* identifier= */ 3, /* name= */ "THREE",
+ createConfig(createDeviceState(3, "THREE"),
(c) -> c.getHingeAngle() > 90f)
);
sendSensorEvent(mHingeAngleSensor, /* value= */ 0f);
@@ -448,8 +434,7 @@
@Test
public void isScreenOn_afterDisplayChangedToOn_returnsTrue() {
createProvider(
- createConfig(
- /* identifier= */ 1, /* name= */ "ONE", (c) -> true)
+ createConfig(createDeviceState(1, "ONE"), (c) -> true)
);
setScreenOn(true);
@@ -460,8 +445,7 @@
@Test
public void isScreenOn_afterDisplayChangedToOff_returnsFalse() {
createProvider(
- createConfig(
- /* identifier= */ 1, /* name= */ "ONE", (c) -> true)
+ createConfig(createDeviceState(1, "ONE"), (c) -> true)
);
setScreenOn(false);
@@ -472,8 +456,7 @@
@Test
public void isScreenOn_afterDisplayChangedToOnThenOff_returnsFalse() {
createProvider(
- createConfig(
- /* identifier= */ 1, /* name= */ "ONE", (c) -> true)
+ createConfig(createDeviceState(1, "ONE"), (c) -> true)
);
setScreenOn(true);
@@ -487,14 +470,14 @@
when(mDisplayManager.getDisplays()).thenReturn(new Display[]{mDefaultDisplay});
when(mDefaultDisplay.getType()).thenReturn(TYPE_INTERNAL);
- createProvider(createConfig(/* identifier= */ 1, /* name= */ "CLOSED",
+ createProvider(createConfig(createDeviceState(1, "CLOSED"),
(c) -> c.getHingeAngle() < 5f),
- createConfig(/* identifier= */ 2, /* name= */ "HALF_OPENED",
+ createConfig(createDeviceState(2, "HALF_OPENED"),
(c) -> c.getHingeAngle() < 90f),
- createConfig(/* identifier= */ 3, /* name= */ "OPENED",
+ createConfig(createDeviceState(3, "OPENED"),
(c) -> c.getHingeAngle() < 180f),
- createConfig(/* identifier= */ 4, /* name= */ "DUAL_DISPLAY", /* flags */ 0,
- (c) -> false, FoldableDeviceStateProvider::hasNoConnectedExternalDisplay));
+ createConfig(createDeviceState(4, "DUAL_DISPLAY"), (c) -> false,
+ FoldableDeviceStateProvider::hasNoConnectedExternalDisplay));
Listener listener = mock(Listener.class);
mProvider.setListener(listener);
@@ -502,10 +485,11 @@
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();
+ createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+ createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(4, "DUAL_DISPLAY",
+ EMPTY_PROPERTY_SET)}).inOrder();
clearInvocations(listener);
@@ -520,9 +504,9 @@
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();
+ createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+ createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET)}).inOrder();
clearInvocations(listener);
// The DUAL_DISPLAY state should be re-enabled.
@@ -532,10 +516,11 @@
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();
+ createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+ createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(4, "DUAL_DISPLAY",
+ EMPTY_PROPERTY_SET)}).inOrder();
}
@Test
@@ -543,14 +528,14 @@
when(mDisplayManager.getDisplays()).thenReturn(new Display[]{mDefaultDisplay});
when(mDefaultDisplay.getType()).thenReturn(TYPE_INTERNAL);
- createProvider(createConfig(/* identifier= */ 1, /* name= */ "CLOSED",
+ createProvider(createConfig(createDeviceState(1, "CLOSED"),
(c) -> c.getHingeAngle() < 5f),
- createConfig(/* identifier= */ 2, /* name= */ "HALF_OPENED",
+ createConfig(createDeviceState(2, "HALF_OPENED"),
(c) -> c.getHingeAngle() < 90f),
- createConfig(/* identifier= */ 3, /* name= */ "OPENED",
+ createConfig(createDeviceState(3, "OPENED"),
(c) -> c.getHingeAngle() < 180f),
- createConfig(/* identifier= */ 4, /* name= */ "DUAL_DISPLAY", /* flags */ 0,
- (c) -> false, FoldableDeviceStateProvider::hasNoConnectedExternalDisplay));
+ createConfig(createDeviceState(4, "DUAL_DISPLAY"),
+ FoldableDeviceStateProvider::hasNoConnectedExternalDisplay));
Listener listener = mock(Listener.class);
mProvider.setListener(listener);
@@ -558,10 +543,11 @@
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();
+ createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+ createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(4, "DUAL_DISPLAY",
+ EMPTY_PROPERTY_SET)}).inOrder();
clearInvocations(listener);
@@ -579,10 +565,8 @@
@Test
public void hasNoConnectedDisplay_afterExternalDisplayAdded_returnsFalse() {
createProvider(
- createConfig(
- /* identifier= */ 1, /* name= */ "ONE",
- /* flags= */0, (c) -> true,
- FoldableDeviceStateProvider::hasNoConnectedExternalDisplay)
+ createConfig(createDeviceState(1, "ONE", Collections.emptySet()),
+ (c) -> true, FoldableDeviceStateProvider::hasNoConnectedExternalDisplay)
);
addExternalDisplay(/* displayId */ 1);
@@ -594,9 +578,8 @@
public void testOnDisplayAddedWithNullDisplayDoesNotThrowNPE() {
createProvider(
createConfig(
- /* identifier= */ 1, /* name= */ "ONE",
- /* flags= */0, (c) -> true,
- FoldableDeviceStateProvider::hasNoConnectedExternalDisplay)
+ createDeviceState(1, "ONE", Collections.emptySet()),
+ (c) -> true, FoldableDeviceStateProvider::hasNoConnectedExternalDisplay)
);
when(mDisplayManager.getDisplay(1)).thenReturn(null);
@@ -608,9 +591,8 @@
public void hasNoConnectedDisplay_afterExternalDisplayAddedAndRemoved_returnsTrue() {
createProvider(
createConfig(
- /* identifier= */ 1, /* name= */ "ONE",
- /* flags= */0, (c) -> true,
- FoldableDeviceStateProvider::hasNoConnectedExternalDisplay)
+ createDeviceState(1, "ONE", Collections.emptySet()),
+ (c) -> true, FoldableDeviceStateProvider::hasNoConnectedExternalDisplay)
);
addExternalDisplay(/* displayId */ 1);
@@ -657,7 +639,7 @@
mProvider.onSensorChanged(event);
}
- private void createProvider(DeviceStateConfiguration... configurations) {
+ private void createProvider(DeviceStatePredicateWrapper... configurations) {
mProvider = new FoldableDeviceStateProvider(mFakeFeatureFlags, mContext, mSensorManager,
mHingeAngleSensor, mHallSensor, mDisplayManager, configurations);
verify(mDisplayManager)
@@ -665,4 +647,23 @@
mDisplayListenerCaptor.capture(),
nullable(Handler.class));
}
+
+ /**
+ * Returns a new {@link DeviceState} object
+ */
+ private DeviceState createDeviceState(int identifier,
+ @NonNull String name,
+ @NonNull Set<@DeviceState.DeviceStateProperties Integer> systemProperties) {
+ return new DeviceState(new DeviceState.Configuration.Builder(identifier, name)
+ .setSystemProperties(systemProperties)
+ .build());
+ }
+
+ /**
+ * Returns a new {@link DeviceState} object
+ */
+ private DeviceState createDeviceState(int identifier,
+ @NonNull String name) {
+ return createDeviceState(identifier, name, Collections.emptySet());
+ }
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 9d95c5b..c6189ed 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -76,6 +76,7 @@
import android.os.StrictMode;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.IStorageManager;
@@ -160,6 +161,7 @@
import com.android.server.notification.NotificationManagerService;
import com.android.server.oemlock.OemLockService;
import com.android.server.om.OverlayManagerService;
+import com.android.server.ondeviceintelligence.OnDeviceIntelligenceManagerService;
import com.android.server.os.BugreportManagerService;
import com.android.server.os.DeviceIdentifiersPolicyService;
import com.android.server.os.NativeTombstoneManagerService;
@@ -1089,6 +1091,7 @@
final Context systemUiContext = activityThread.getSystemUiContext();
systemUiContext.setTheme(DEFAULT_SYSTEM_THEME);
+ Trace.registerWithPerfetto();
}
/**
@@ -1132,7 +1135,7 @@
ServiceManager.addService(Context.PLATFORM_COMPAT_SERVICE, platformCompat);
ServiceManager.addService(Context.PLATFORM_COMPAT_NATIVE_SERVICE,
new PlatformCompatNative(platformCompat));
- AppCompatCallbacks.install(new long[0]);
+ AppCompatCallbacks.install(new long[0], new long[0]);
t.traceEnd();
// FileIntegrityService responds to requests from apps and the system. It needs to run after
@@ -1963,6 +1966,7 @@
startSystemCaptionsManagerService(context, t);
startTextToSpeechManagerService(context, t);
startWearableSensingService(t);
+ startOnDeviceIntelligenceService(t);
if (deviceHasConfigString(
context, R.string.config_defaultAmbientContextDetectionService)) {
@@ -3333,6 +3337,12 @@
t.traceEnd(); // startOtherServices
}
+ private void startOnDeviceIntelligenceService(TimingsTraceAndSlog t) {
+ t.traceBegin("startOnDeviceIntelligenceManagerService");
+ mSystemServiceManager.startService(OnDeviceIntelligenceManagerService.class);
+ t.traceEnd();
+ }
+
/**
* Starts system services defined in apexes.
*
diff --git a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
index acaec21..fd2e8c8 100644
--- a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
@@ -27,9 +27,11 @@
import com.android.server.SystemConfig
import com.android.server.SystemService
import com.android.server.appop.AppOpsCheckingServiceInterface
+import com.android.server.permission.PermissionManagerLocal
import com.android.server.permission.access.appop.AppOpService
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.permission.PermissionManagerLocalImpl
import com.android.server.permission.access.permission.PermissionService
import com.android.server.pm.KnownPackages
import com.android.server.pm.PackageManagerLocal
@@ -63,6 +65,11 @@
LocalServices.addService(AppOpsCheckingServiceInterface::class.java, appOpService)
LocalServices.addService(PermissionManagerServiceInterface::class.java, permissionService)
+
+ LocalManagerRegistry.addManager(
+ PermissionManagerLocal::class.java,
+ PermissionManagerLocalImpl(this)
+ )
}
fun initialize() {
diff --git a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
index d0913d2..58714a8 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
@@ -25,9 +25,9 @@
import android.util.Slog
import android.util.SparseLongArray
import com.android.internal.annotations.GuardedBy
-import com.android.internal.os.BackgroundThread
import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
+import com.android.server.IoThread
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
import com.android.server.permission.access.util.PermissionApex
@@ -47,7 +47,7 @@
private val writeLock = Any()
fun initialize() {
- writeHandler = WriteHandler(BackgroundThread.getHandler().looper)
+ writeHandler = WriteHandler(IoThread.getHandler().looper)
}
/**
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index 67df67f..af8ce31 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -63,6 +63,12 @@
private val privilegedPermissionAllowlistViolations = MutableIndexedSet<String>()
+ /**
+ * Test-only switch to enforce signature permission allowlist even on debuggable builds.
+ */
+ @Volatile
+ var isSignaturePermissionAllowlistForceEnforced = false
+
override val subjectScheme: String
get() = UidUri.SCHEME
@@ -1274,7 +1280,7 @@
SigningDetails.CertCapabilities.PERMISSION
)
if (!Flags.signaturePermissionAllowlistEnabled()) {
- return hasCommonSigner;
+ return hasCommonSigner
}
if (!hasCommonSigner) {
return false
@@ -1308,7 +1314,7 @@
" ${packageState.packageName} (${packageState.path}) not in" +
" signature permission allowlist"
)
- if (!Build.isDebuggable()) {
+ if (!Build.isDebuggable() || isSignaturePermissionAllowlistForceEnforced) {
return false
}
}
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
index c5c921d..7c75138 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
@@ -477,9 +477,7 @@
if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT)) {
flags = flags or UPGRADE_EXEMPT
}
- // We ignore whether FLAG_PERMISSION_APPLY_RESTRICTION is set here because previously
- // platform may be relying on the old restorePermissionState() to get it correct later.
- if (!flags.hasAnyBit(MASK_EXEMPT)) {
+ if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION)) {
if (permission.isHardRestricted) {
flags = flags or RESTRICTION_REVOKED
}
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionManagerLocalImpl.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionManagerLocalImpl.kt
new file mode 100644
index 0000000..ad2d70bb
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionManagerLocalImpl.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.permission.access.permission
+
+import android.os.Build
+import com.android.server.permission.PermissionManagerLocal
+import com.android.server.permission.access.AccessCheckingService
+import com.android.server.permission.access.PermissionUri
+import com.android.server.permission.access.UidUri
+
+class PermissionManagerLocalImpl(
+ private val service: AccessCheckingService
+) : PermissionManagerLocal {
+ private val policy =
+ service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as AppIdPermissionPolicy
+
+ override fun isSignaturePermissionAllowlistForceEnforced(): Boolean {
+ check(Build.isDebuggable())
+ return policy.isSignaturePermissionAllowlistForceEnforced
+ }
+
+ override fun setSignaturePermissionAllowlistForceEnforced(forceEnforced: Boolean) {
+ check(Build.isDebuggable())
+ policy.isSignaturePermissionAllowlistForceEnforced = forceEnforced
+ }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index f4d95af..b9c5b36 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -201,6 +201,7 @@
when(mMockUserManagerInternal.isUserRunning(anyInt())).thenReturn(true);
when(mMockUserManagerInternal.getProfileIds(anyInt(), anyBoolean()))
.thenReturn(new int[] {0});
+ when(mMockUserManagerInternal.getUserIds()).thenReturn(new int[] {0});
when(mMockActivityManagerInternal.isSystemReady()).thenReturn(true);
when(mMockPackageManagerInternal.getPackageUid(anyString(), anyLong(), anyInt()))
.thenReturn(Binder.getCallingUid());
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
index a33e52f..e5d3153 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
@@ -91,8 +91,8 @@
@Test
public void testSoftInputShowHideHistoryDump_withNulls_doesntThrow() {
var writer = new StringWriter();
- var history = new InputMethodManagerService.SoftInputShowHideHistory();
- history.addEntry(new InputMethodManagerService.SoftInputShowHideHistory.Entry(
+ var history = new SoftInputShowHideHistory();
+ history.addEntry(new SoftInputShowHideHistory.Entry(
null,
null,
null,
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index 4f27bc2..ea7bb8b 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -94,7 +94,7 @@
"libutils",
"netd_aidl_interface-V5-cpp",
],
-
+ compile_multilib: "both",
dxflags: ["--multi-dex"],
java_resources: [
diff --git a/services/tests/VpnTests/Android.bp b/services/tests/VpnTests/Android.bp
index 64a9a3b..a5011a8 100644
--- a/services/tests/VpnTests/Android.bp
+++ b/services/tests/VpnTests/Android.bp
@@ -17,8 +17,7 @@
"java/**/*.java",
"java/**/*.kt",
],
-
- defaults: ["framework-connectivity-test-defaults"],
+ sdk_version: "core_platform", // tests can use @CorePlatformApi's
test_suites: ["device-tests"],
static_libs: [
"androidx.test.rules",
@@ -32,6 +31,13 @@
"service-connectivity-tiramisu-pre-jarjar",
],
libs: [
+ // order matters: classes in framework-connectivity are resolved before framework,
+ // meaning @hide APIs in framework-connectivity are resolved before @SystemApi
+ // stubs in framework
+ "framework-connectivity.impl",
+ "framework-connectivity-t.impl",
+ "framework",
+ "framework-res",
"android.test.runner",
"android.test.base",
"android.test.mock",
diff --git a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 9c9aeea..705dac5 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -19,6 +19,7 @@
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
import static org.junit.Assert.assertArrayEquals;
@@ -72,7 +73,7 @@
private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG = 4000;
private static final int BRIGHTENING_LIGHT_DEBOUNCE_CONFIG_IDLE = 1000;
private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG_IDLE = 2000;
- private static final float DOZE_SCALE_FACTOR = 0.0f;
+ private static final float DOZE_SCALE_FACTOR = 0.54f;
private static final boolean RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG = false;
private static final int LIGHT_SENSOR_WARMUP_TIME = 0;
private static final int AMBIENT_LIGHT_HORIZON_SHORT = 1000;
@@ -87,6 +88,7 @@
@Mock SensorManager mSensorManager;
@Mock BrightnessMappingStrategy mBrightnessMappingStrategy;
@Mock BrightnessMappingStrategy mIdleBrightnessMappingStrategy;
+ @Mock BrightnessMappingStrategy mDozeBrightnessMappingStrategy;
@Mock HysteresisLevels mAmbientBrightnessThresholds;
@Mock HysteresisLevels mScreenBrightnessThresholds;
@Mock HysteresisLevels mAmbientBrightnessThresholdsIdle;
@@ -124,12 +126,15 @@
when(mBrightnessMappingStrategy.getMode()).thenReturn(AUTO_BRIGHTNESS_MODE_DEFAULT);
when(mIdleBrightnessMappingStrategy.getMode()).thenReturn(AUTO_BRIGHTNESS_MODE_IDLE);
+ when(mDozeBrightnessMappingStrategy.getMode()).thenReturn(AUTO_BRIGHTNESS_MODE_DOZE);
SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap = new SparseArray<>();
brightnessMappingStrategyMap.append(AUTO_BRIGHTNESS_MODE_DEFAULT,
mBrightnessMappingStrategy);
brightnessMappingStrategyMap.append(AUTO_BRIGHTNESS_MODE_IDLE,
mIdleBrightnessMappingStrategy);
+ brightnessMappingStrategyMap.append(AUTO_BRIGHTNESS_MODE_DOZE,
+ mDozeBrightnessMappingStrategy);
mController = new AutomaticBrightnessController(
new AutomaticBrightnessController.Injector() {
@Override
@@ -1032,10 +1037,9 @@
float normalizedBrightness = 0.3f;
when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux);
when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux);
- when(mBrightnessMappingStrategy.getBrightness(eq(lux), eq(null), anyInt()))
- .thenReturn(normalizedBrightness);
+ when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null),
+ /* category= */ anyInt())).thenReturn(normalizedBrightness);
when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
- when(mBrightnessThrottler.isThrottled()).thenReturn(true);
// Send a new sensor value, disable the sensor and verify
listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
@@ -1047,4 +1051,79 @@
mController.getAutomaticScreenBrightnessBasedOnLastObservedLux(
/* brightnessEvent= */ null), EPSILON);
}
+
+ @Test
+ public void testAutoBrightnessInDoze_ShouldScaleIfNotUsingDozeCurve() throws Exception {
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // Set up system to return 0.3f as a brightness value
+ float lux = 100.0f;
+ // Brightness as float (from 0.0f to 1.0f)
+ float normalizedBrightness = 0.3f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux);
+ when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null),
+ /* category= */ anyInt())).thenReturn(normalizedBrightness);
+ when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
+
+ // Set policy to DOZE
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
+ /* brightness= */ 0, /* userChangedBrightness= */ false, /* adjustment= */ 0,
+ /* userChanged= */ false, DisplayPowerRequest.POLICY_DOZE,
+ /* shouldResetShortTermModel= */ true);
+
+ // Send a new sensor value
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
+
+ // The brightness should be scaled by the doze factor
+ assertEquals(normalizedBrightness * DOZE_SCALE_FACTOR,
+ mController.getAutomaticScreenBrightness(
+ /* brightnessEvent= */ null), EPSILON);
+ assertEquals(normalizedBrightness * DOZE_SCALE_FACTOR,
+ mController.getAutomaticScreenBrightnessBasedOnLastObservedLux(
+ /* brightnessEvent= */ null), EPSILON);
+ }
+
+ @Test
+ public void testAutoBrightnessInDoze_ShouldNotScaleIfUsingDozeCurve() throws Exception {
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // Set up system to return 0.3f as a brightness value
+ float lux = 100.0f;
+ // Brightness as float (from 0.0f to 1.0f)
+ float normalizedBrightness = 0.3f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux);
+ when(mDozeBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null),
+ /* category= */ anyInt())).thenReturn(normalizedBrightness);
+ when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
+
+ // Switch mode to DOZE
+ mController.switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
+
+ // Set policy to DOZE
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
+ /* brightness= */ 0, /* userChangedBrightness= */ false, /* adjustment= */ 0,
+ /* userChanged= */ false, DisplayPowerRequest.POLICY_DOZE,
+ /* shouldResetShortTermModel= */ true);
+
+ // Send a new sensor value
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
+
+ // The brightness should not be scaled by the doze factor
+ assertEquals(normalizedBrightness,
+ mController.getAutomaticScreenBrightness(
+ /* brightnessEvent= */ null), EPSILON);
+ assertEquals(normalizedBrightness,
+ mController.getAutomaticScreenBrightnessBasedOnLastObservedLux(
+ /* brightnessEvent= */ null), EPSILON);
+ }
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt
new file mode 100644
index 0000000..1f3184d
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display
+
+import android.os.IBinder
+import androidx.test.filters.SmallTest
+import com.android.server.display.brightness.clamper.HdrClamper
+import com.android.server.display.feature.DisplayManagerFlags
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+private const val MAX_BRIGHTNESS = 1.0f
+private const val TRANSITION_POINT = 0.7f
+private const val NORMAL_BRIGHTNESS_HIGH = 0.8f
+private const val NORMAL_BRIGHTNESS_LOW = 0.6f
+
+@SmallTest
+class BrightnessRangeControllerTest {
+
+ private val mockHbmController = mock<HighBrightnessModeController>()
+ private val mockCallback = mock<Runnable>()
+ private val mockConfig = mock<DisplayDeviceConfig>()
+ private val mockNormalBrightnessController = mock<NormalBrightnessModeController>()
+ private val mockHdrClamper = mock<HdrClamper>()
+ private val mockFlags = mock<DisplayManagerFlags>()
+ private val mockToken = mock<IBinder>()
+
+ @Test
+ fun `returns HBC max brightness if HBM supported and ON`() {
+ val controller = createController()
+ assertThat(controller.currentBrightnessMax).isEqualTo(MAX_BRIGHTNESS)
+ }
+
+ @Test
+ fun `returns NBC max brightness if device does not support HBM`() {
+ val controller = createController(hbmSupported = false)
+ assertThat(controller.currentBrightnessMax).isEqualTo(NORMAL_BRIGHTNESS_LOW)
+ }
+
+ @Test
+ fun `returns NBC max brightness if HBM not allowed`() {
+ val controller = createController(hbmAllowed = false)
+ assertThat(controller.currentBrightnessMax).isEqualTo(NORMAL_BRIGHTNESS_LOW)
+ }
+
+ @Test
+ fun `returns HBC max brightness if NBM is disabled`() {
+ val controller = createController(nbmEnabled = false, hbmAllowed = false)
+ assertThat(controller.currentBrightnessMax).isEqualTo(MAX_BRIGHTNESS)
+ }
+
+ @Test
+ fun `returns HBC max brightness if lower than NBC max brightness`() {
+ val controller = createController(
+ hbmAllowed = false,
+ hbmMaxBrightness = TRANSITION_POINT,
+ nbmMaxBrightness = NORMAL_BRIGHTNESS_HIGH
+ )
+ assertThat(controller.currentBrightnessMax).isEqualTo(TRANSITION_POINT)
+ }
+
+ private fun createController(
+ nbmEnabled: Boolean = true,
+ hbmSupported: Boolean = true,
+ hbmAllowed: Boolean = true,
+ hbmMaxBrightness: Float = MAX_BRIGHTNESS,
+ nbmMaxBrightness: Float = NORMAL_BRIGHTNESS_LOW
+ ): BrightnessRangeController {
+ whenever(mockFlags.isNbmControllerEnabled).thenReturn(nbmEnabled)
+ whenever(mockHbmController.deviceSupportsHbm()).thenReturn(hbmSupported)
+ whenever(mockHbmController.isHbmCurrentlyAllowed).thenReturn(hbmAllowed)
+ whenever(mockHbmController.currentBrightnessMax).thenReturn(hbmMaxBrightness)
+ whenever(mockNormalBrightnessController.currentBrightnessMax).thenReturn(nbmMaxBrightness)
+
+ return BrightnessRangeController(mockHbmController, mockCallback, mockConfig,
+ mockNormalBrightnessController, mockHdrClamper, mockFlags, mockToken,
+ DisplayDeviceInfo())
+ }
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
index dc6abf1..1c71abc 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
@@ -52,7 +52,9 @@
private static final int WIDTH = 500;
private static final int HEIGHT = 900;
private static final Point PORTRAIT_SIZE = new Point(WIDTH, HEIGHT);
+ private static final Point PORTRAIT_DOUBLE_WIDTH = new Point(2 * WIDTH, HEIGHT);
private static final Point LANDSCAPE_SIZE = new Point(HEIGHT, WIDTH);
+ private static final Point LANDSCAPE_DOUBLE_HEIGHT = new Point(HEIGHT, 2 * WIDTH);
@Mock
private SurfaceControl.Transaction mMockTransaction;
@@ -69,6 +71,16 @@
}
@Test
+ public void testGetDisplaySurfaceDefaultSizeLocked_notRotated_anisotropyCorrection() {
+ mDisplayDeviceInfo.xDpi = 0.5f;
+ mDisplayDeviceInfo.yDpi = 1.0f;
+ DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+ mMockDisplayAdapter, /*isAnisotropyCorrectionEnabled=*/ true);
+ assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(
+ PORTRAIT_DOUBLE_WIDTH);
+ }
+
+ @Test
public void testGetDisplaySurfaceDefaultSizeLocked_notRotated() {
DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
mMockDisplayAdapter);
@@ -84,6 +96,17 @@
}
@Test
+ public void testGetDisplaySurfaceDefaultSizeLocked_rotation90_anisotropyCorrection() {
+ mDisplayDeviceInfo.xDpi = 0.5f;
+ mDisplayDeviceInfo.yDpi = 1.0f;
+ DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+ mMockDisplayAdapter, /*isAnisotropyCorrectionEnabled=*/ true);
+ displayDevice.setProjectionLocked(mMockTransaction, ROTATION_90, new Rect(), new Rect());
+ assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(
+ LANDSCAPE_DOUBLE_HEIGHT);
+ }
+
+ @Test
public void testGetDisplaySurfaceDefaultSizeLocked_rotation90() {
DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
mMockDisplayAdapter);
@@ -111,8 +134,14 @@
private final DisplayDeviceInfo mDisplayDeviceInfo;
FakeDisplayDevice(DisplayDeviceInfo displayDeviceInfo, DisplayAdapter displayAdapter) {
+ this(displayDeviceInfo, displayAdapter, /*isAnisotropyCorrectionEnabled=*/ false);
+ }
+
+ FakeDisplayDevice(DisplayDeviceInfo displayDeviceInfo, DisplayAdapter displayAdapter,
+ boolean isAnisotropyCorrectionEnabled) {
super(displayAdapter, /* displayToken= */ null, /* uniqueId= */ "",
- InstrumentationRegistry.getInstrumentation().getContext());
+ InstrumentationRegistry.getInstrumentation().getContext(),
+ isAnisotropyCorrectionEnabled);
mDisplayDeviceInfo = displayDeviceInfo;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index b142334..18f0311 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -2408,6 +2408,7 @@
when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
manageDisplaysPermission(/* granted= */ true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
DisplayManagerInternal localService = displayManager.new LocalService();
DisplayManagerService.BinderService bs = displayManager.new BinderService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
@@ -2440,6 +2441,7 @@
.when(() -> SystemProperties.getBoolean(ENABLE_ON_CONNECT, false));
manageDisplaysPermission(/* granted= */ true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
DisplayManagerInternal localService = displayManager.new LocalService();
DisplayManagerService.BinderService bs = displayManager.new BinderService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
@@ -2487,6 +2489,7 @@
when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
manageDisplaysPermission(/* granted= */ true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
DisplayManagerService.BinderService bs = displayManager.new BinderService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
@@ -2652,6 +2655,7 @@
when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
manageDisplaysPermission(/* granted= */ true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
DisplayManagerService.BinderService bs = displayManager.new BinderService();
DisplayManagerInternal localService = displayManager.new LocalService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
@@ -2699,6 +2703,7 @@
when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
manageDisplaysPermission(/* granted= */ true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
DisplayManagerService.BinderService bs = displayManager.new BinderService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 14d8a9c..fb23213 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -114,6 +114,8 @@
private static final int SECOND_FOLLOWER_DISPLAY_ID = FOLLOWER_DISPLAY_ID + 1;
private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
private static final float PROX_SENSOR_MAX_RANGE = 5;
+ private static final float DOZE_SCALE_FACTOR = 0.34f;
+
private static final float BRIGHTNESS_RAMP_RATE_MINIMUM = 0.0f;
private static final float BRIGHTNESS_RAMP_RATE_FAST_DECREASE = 0.3f;
private static final float BRIGHTNESS_RAMP_RATE_FAST_INCREASE = 0.4f;
@@ -193,6 +195,9 @@
mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.bool.config_displayColorFadeDisabled, false);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor,
+ DOZE_SCALE_FACTOR);
doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
SystemProperties.set(anyString(), any()));
@@ -1705,7 +1710,7 @@
}
@Test
- public void testInitialDozeBrightness() {
+ public void testInitialDozeBrightness_AutoBrightnessEnabled() {
when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true);
when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
@@ -1734,6 +1739,39 @@
}
@Test
+ public void testInitialDozeBrightness_AutoBrightnessDisabled() {
+ when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
+ mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+ float brightness = 0.277f;
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ when(mHolder.hbmController.getCurrentBrightnessMax())
+ .thenReturn(PowerManager.BRIGHTNESS_MAX);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState, initialize
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(brightness * DOZE_SCALE_FACTOR),
+ /* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(),
+ /* ignoreAnimationLimits= */ anyBoolean());
+ assertEquals(brightness * DOZE_SCALE_FACTOR, mHolder.dpc.getDozeBrightnessForOffload(),
+ /* delta= */ 0);
+ }
+
+ @Test
public void testInitialDozeBrightness_AbcIsNull() {
when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true);
when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
@@ -1889,6 +1927,8 @@
mock(ScreenOffBrightnessSensorController.class);
final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class);
final HdrClamper hdrClamper = mock(HdrClamper.class);
+ final NormalBrightnessModeController normalBrightnessModeController = mock(
+ NormalBrightnessModeController.class);
BrightnessClamperController clamperController = mock(BrightnessClamperController.class);
when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
@@ -1901,7 +1941,8 @@
TestInjector injector = spy(new TestInjector(displayPowerState, animator,
automaticBrightnessController, wakelockController, brightnessMappingStrategy,
- hysteresisLevels, screenOffBrightnessSensorController, hbmController, hdrClamper,
+ hysteresisLevels, screenOffBrightnessSensorController,
+ hbmController, normalBrightnessModeController, hdrClamper,
clamperController, mDisplayManagerFlagsMock));
final LogicalDisplay display = mock(LogicalDisplay.class);
@@ -1989,6 +2030,8 @@
private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
private final HighBrightnessModeController mHighBrightnessModeController;
+ private final NormalBrightnessModeController mNormalBrightnessModeController;
+
private final HdrClamper mHdrClamper;
private final BrightnessClamperController mClamperController;
@@ -2002,6 +2045,7 @@
HysteresisLevels hysteresisLevels,
ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
HighBrightnessModeController highBrightnessModeController,
+ NormalBrightnessModeController normalBrightnessModeController,
HdrClamper hdrClamper,
BrightnessClamperController clamperController,
DisplayManagerFlags flags) {
@@ -2013,6 +2057,7 @@
mHysteresisLevels = hysteresisLevels;
mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
mHighBrightnessModeController = highBrightnessModeController;
+ mNormalBrightnessModeController = normalBrightnessModeController;
mHdrClamper = hdrClamper;
mClamperController = clamperController;
mFlags = flags;
@@ -2125,7 +2170,8 @@
DisplayDeviceConfig displayDeviceConfig, Handler handler,
DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) {
return new BrightnessRangeController(hbmController, modeChangeCallback,
- displayDeviceConfig, mHdrClamper, mFlags, displayToken, info);
+ displayDeviceConfig, mNormalBrightnessModeController, mHdrClamper,
+ mFlags, displayToken, info);
}
@Override
diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
index 1529a08..1a71e77 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
@@ -228,13 +228,27 @@
@Test
public void testOnExternalDisplayAvailable() {
- when(mMockedLogicalDisplay.isEnabledLocked()).thenReturn(false);
+
mExternalDisplayPolicy.handleExternalDisplayConnectedLocked(mMockedLogicalDisplay);
+ assertNotAskedToEnableDisplay();
+ verify(mMockedExternalDisplayStatsService, never()).onDisplayConnected(any());
+
+ mExternalDisplayPolicy.onBootCompleted();
assertAskedToEnableDisplay();
verify(mMockedExternalDisplayStatsService).onDisplayConnected(eq(mMockedLogicalDisplay));
}
@Test
+ public void testOnExternalDisplayUnpluggedBeforeBootCompletes() {
+ mExternalDisplayPolicy.handleExternalDisplayConnectedLocked(mMockedLogicalDisplay);
+ mExternalDisplayPolicy.handleLogicalDisplayDisconnectedLocked(mMockedLogicalDisplay);
+ mExternalDisplayPolicy.onBootCompleted();
+ assertNotAskedToEnableDisplay();
+ verify(mMockedExternalDisplayStatsService, never()).onDisplayConnected(any());
+ verify(mMockedExternalDisplayStatsService, never()).onDisplayDisconnected(anyInt());
+ }
+
+ @Test
public void testOnExternalDisplayAvailable_criticalThermalCondition()
throws RemoteException {
// Disallow external displays due to thermals.
@@ -303,8 +317,14 @@
mDisplayEventCaptor.capture());
assertThat(mLogicalDisplayCaptor.getValue()).isEqualTo(mMockedLogicalDisplay);
assertThat(mDisplayEventCaptor.getValue()).isEqualTo(EVENT_DISPLAY_CONNECTED);
+ verify(mMockedLogicalDisplay).setEnabledLocked(false);
clearInvocations(mMockedLogicalDisplayMapper);
- when(mMockedLogicalDisplay.isEnabledLocked()).thenReturn(true);
+ clearInvocations(mMockedLogicalDisplay);
+ }
+
+ private void assertNotAskedToEnableDisplay() {
+ verify(mMockedInjector, never()).sendExternalDisplayEventLocked(any(), anyInt());
+ verify(mMockedLogicalDisplay, never()).setEnabledLocked(anyBoolean());
}
private void assertIsExternalDisplayAllowed(final boolean enabled) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index bed6f92..2939192 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -16,7 +16,7 @@
package com.android.server.display;
-import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.DEFAULT_DISPLAY_GROUP;
import static android.view.Display.FLAG_REAR;
@@ -674,7 +674,7 @@
/* isInteractive= */true,
/* isBootCompleted= */true));
assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
- INVALID_DEVICE_STATE,
+ INVALID_DEVICE_STATE_IDENTIFIER,
/* isOverrideActive= */false,
/* isInteractive= */true,
/* isBootCompleted= */true));
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
index 1c43418..549f0d7 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
@@ -106,8 +106,184 @@
}
@Test
+ public void testLetterbox() {
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+ /*isAnisotropyCorrectionEnabled=*/ false);
+ mDisplayDeviceInfo.xDpi = 0.5f;
+ mDisplayDeviceInfo.yDpi = 1.0f;
+
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+ var originalDisplayInfo = mLogicalDisplay.getDisplayInfoLocked();
+ assertEquals(DISPLAY_WIDTH, originalDisplayInfo.logicalWidth);
+ assertEquals(DISPLAY_HEIGHT, originalDisplayInfo.logicalHeight);
+
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+ assertEquals(new Point(0, 0), mLogicalDisplay.getDisplayPosition());
+
+ /*
+ * Content is too wide, should become letterboxed
+ * ______DISPLAY_WIDTH________
+ * | |
+ * |________________________|
+ * | |
+ * | CONTENT |
+ * | |
+ * |________________________|
+ * | |
+ * |________________________|
+ */
+ // Make a wide application content, by reducing its height.
+ DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = DISPLAY_WIDTH;
+ displayInfo.logicalHeight = DISPLAY_HEIGHT / 2;
+ mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+ assertEquals(new Point(0, DISPLAY_HEIGHT / 4), mLogicalDisplay.getDisplayPosition());
+ }
+
+ @Test
+ public void testNoLetterbox_anisotropyCorrection() {
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+ /*isAnisotropyCorrectionEnabled=*/ true);
+
+ // In case of Anisotropy of pixels, then the content should be rescaled so it would adjust
+ // to using the whole screen. This is because display will rescale it back to fill the
+ // screen (in case the display menu setting is set to stretch the pixels across the display)
+ mDisplayDeviceInfo.xDpi = 0.5f;
+ mDisplayDeviceInfo.yDpi = 1.0f;
+
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+ var originalDisplayInfo = mLogicalDisplay.getDisplayInfoLocked();
+ // Content width re-scaled
+ assertEquals(DISPLAY_WIDTH * 2, originalDisplayInfo.logicalWidth);
+ assertEquals(DISPLAY_HEIGHT, originalDisplayInfo.logicalHeight);
+
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+ // Applications need to think that they are shown on a display with square pixels.
+ // as applications can be displayed on multiple displays simultaneously (mirrored).
+ // Content is too wide, should have become letterboxed - but it won't because of anisotropy
+ // correction
+ assertEquals(new Point(0, 0), mLogicalDisplay.getDisplayPosition());
+ }
+
+ @Test
+ public void testLetterbox_anisotropyCorrectionYDpi() {
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+ /*isAnisotropyCorrectionEnabled=*/ true);
+
+ DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = DISPLAY_WIDTH;
+ displayInfo.logicalHeight = DISPLAY_HEIGHT / 2;
+ mDisplayDeviceInfo.xDpi = 1.0f;
+ mDisplayDeviceInfo.yDpi = 0.5f;
+ mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+ assertEquals(new Point(0, 75), mLogicalDisplay.getDisplayPosition());
+ }
+
+ @Test
+ public void testPillarbox() {
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+ /*isAnisotropyCorrectionEnabled=*/ false);
+ mDisplayDeviceInfo.xDpi = 0.5f;
+ mDisplayDeviceInfo.yDpi = 1.0f;
+
+ DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.rotation = Surface.ROTATION_90;
+ displayInfo.logicalWidth = DISPLAY_WIDTH;
+ displayInfo.logicalHeight = DISPLAY_HEIGHT;
+ mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
+ mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+
+ var updatedDisplayInfo = mLogicalDisplay.getDisplayInfoLocked();
+ assertEquals(Surface.ROTATION_90, updatedDisplayInfo.rotation);
+ assertEquals(DISPLAY_WIDTH, updatedDisplayInfo.logicalWidth);
+ assertEquals(DISPLAY_HEIGHT, updatedDisplayInfo.logicalHeight);
+
+ /*
+ * Content is too tall, should become pillarboxed
+ * ______DISPLAY_WIDTH________
+ * | | | |
+ * | | | |
+ * | | | |
+ * | | CONTENT | |
+ * | | | |
+ * | | | |
+ * |____|________________|____|
+ */
+
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+ assertEquals(new Point(75, 0), mLogicalDisplay.getDisplayPosition());
+ }
+
+ @Test
+ public void testPillarbox_anisotropyCorrection() {
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+ /*isAnisotropyCorrectionEnabled=*/ true);
+
+ DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = DISPLAY_WIDTH;
+ displayInfo.logicalHeight = DISPLAY_HEIGHT;
+ displayInfo.rotation = Surface.ROTATION_90;
+ mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
+ // In case of Anisotropy of pixels, then the content should be rescaled so it would adjust
+ // to using the whole screen. This is because display will rescale it back to fill the
+ // screen (in case the display menu setting is set to stretch the pixels across the display)
+ mDisplayDeviceInfo.xDpi = 0.5f;
+ mDisplayDeviceInfo.yDpi = 1.0f;
+ mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+ // Applications need to think that they are shown on a display with square pixels.
+ // as applications can be displayed on multiple displays simultaneously (mirrored).
+ // Content is a bit wider than in #testPillarbox, due to content added stretching
+ assertEquals(new Point(50, 0), mLogicalDisplay.getDisplayPosition());
+ }
+
+ @Test
+ public void testNoPillarbox_anisotropyCorrectionYDpi() {
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+ /*isAnisotropyCorrectionEnabled=*/ true);
+
+ // In case of Anisotropy of pixels, then the content should be rescaled so it would adjust
+ // to using the whole screen. This is because display will rescale it back to fill the
+ // screen (in case the display menu setting is set to stretch the pixels across the display)
+ mDisplayDeviceInfo.xDpi = 1.0f;
+ mDisplayDeviceInfo.yDpi = 0.5f;
+
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+ var originalDisplayInfo = mLogicalDisplay.getDisplayInfoLocked();
+ // Content width re-scaled
+ assertEquals(DISPLAY_WIDTH, originalDisplayInfo.logicalWidth);
+ assertEquals(DISPLAY_HEIGHT * 2, originalDisplayInfo.logicalHeight);
+
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+ // Applications need to think that they are shown on a display with square pixels.
+ // as applications can be displayed on multiple displays simultaneously (mirrored).
+ // Content is too tall, should have occupy the whole screen - but it won't because of
+ // anisotropy correction
+ assertEquals(new Point(0, 0), mLogicalDisplay.getDisplayPosition());
+ }
+
+ @Test
public void testGetDisplayPosition() {
- Point expectedPosition = new Point();
+ Point expectedPosition = new Point(0, 0);
SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
index 0e89d83..a3728c8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -18,8 +18,10 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ContentResolver;
@@ -268,4 +270,33 @@
mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
Display.STATE_ON));
}
+
+ @Test
+ public void selectStrategyCallsPostProcessorForAllStrategies() {
+ when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true);
+ mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
+ mInjector, DISPLAY_ID, mDisplayManagerFlags);
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+ when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(0.3f);
+
+ mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest, Display.STATE_ON);
+
+ StrategySelectionNotifyRequest strategySelectionNotifyRequest =
+ new StrategySelectionNotifyRequest(mFollowerBrightnessStrategy);
+ verify(mInvalidBrightnessStrategy).strategySelectionPostProcessor(
+ eq(strategySelectionNotifyRequest));
+ verify(mScreenOffBrightnessModeStrategy).strategySelectionPostProcessor(
+ eq(strategySelectionNotifyRequest));
+ verify(mDozeBrightnessModeStrategy).strategySelectionPostProcessor(
+ eq(strategySelectionNotifyRequest));
+ verify(mFollowerBrightnessStrategy).strategySelectionPostProcessor(
+ eq(strategySelectionNotifyRequest));
+ verify(mBoostBrightnessStrategy).strategySelectionPostProcessor(
+ eq(strategySelectionNotifyRequest));
+ verify(mOverrideBrightnessStrategy).strategySelectionPostProcessor(
+ eq(strategySelectionNotifyRequest));
+ verify(mTemporaryBrightnessStrategy).strategySelectionPostProcessor(
+ eq(strategySelectionNotifyRequest));
+ }
}
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 64076e6..3eced7f 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
@@ -1631,12 +1631,25 @@
director.start(sensorManager);
director.injectSupportedModesByDisplay(supportedModesByDisplay);
- setPeakRefreshRate(Float.POSITIVE_INFINITY);
+ // Disable Smooth Display
+ setPeakRefreshRate(RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
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(vote1, /* frameRateLow= */ 0,
+ /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+ assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0,
+ /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+
+ // Enable Smooth Display
+ setPeakRefreshRate(Float.POSITIVE_INFINITY);
+
+ vote1 = director.getVote(DISPLAY_ID,
+ Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+ vote2 = director.getVote(DISPLAY_ID_2,
+ Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0, /* frameRateHigh= */ 130);
assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0, /* frameRateHigh= */ 140);
}
@@ -1654,10 +1667,18 @@
SensorManager sensorManager = createMockSensorManager(lightSensor);
director.start(sensorManager);
- setPeakRefreshRate(peakRefreshRate);
+ // Disable Smooth Display
+ setPeakRefreshRate(RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0,
+ /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+
+ // Enable Smooth Display
+ setPeakRefreshRate(peakRefreshRate);
+
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+ assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0,
/* frameRateHigh= */ peakRefreshRate);
}
@@ -1759,11 +1780,23 @@
director.start(sensorManager);
director.injectSupportedModesByDisplay(supportedModesByDisplay);
- setMinRefreshRate(Float.POSITIVE_INFINITY);
+ // Disable Force Peak Refresh Rate
+ setMinRefreshRate(0);
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(vote1, /* frameRateLow= */ 0,
+ /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+ assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0,
+ /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+
+ // Enable Force Peak Refresh Rate
+ setMinRefreshRate(Float.POSITIVE_INFINITY);
+
+ vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+ vote2 = director.getVote(DISPLAY_ID_2,
+ Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 130,
/* frameRateHigh= */ Float.POSITIVE_INFINITY);
assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 140,
@@ -1783,9 +1816,17 @@
SensorManager sensorManager = createMockSensorManager(lightSensor);
director.start(sensorManager);
- setMinRefreshRate(minRefreshRate);
+ // Disable Force Peak Refresh Rate
+ setMinRefreshRate(0);
Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
+ assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ 0,
+ /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+
+ // Enable Force Peak Refresh Rate
+ setMinRefreshRate(minRefreshRate);
+
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE);
assertVoteForRenderFrameRateRange(vote, /* frameRateLow= */ minRefreshRate,
/* frameRateHigh= */ Float.POSITIVE_INFINITY);
}
@@ -1829,6 +1870,58 @@
}
@Test
+ public void testPeakAndMinRefreshRate_FlagEnabled_DisplayWithOneMode() {
+ when(mDisplayManagerFlags.isBackUpSmoothDisplayAndForcePeakRefreshRateEnabled())
+ .thenReturn(true);
+ DisplayModeDirector director =
+ new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags);
+ director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+ Display.Mode[] modes1 = new Display.Mode[] {
+ new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 60),
+ new Display.Mode(/* modeId= */ 2, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 130),
+ };
+ Display.Mode[] modes2 = new Display.Mode[] {
+ new Display.Mode(/* modeId= */ 1, /* width= */ 1280, /* height= */ 720,
+ /* refreshRate= */ 60),
+ };
+ SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>();
+ supportedModesByDisplay.put(DISPLAY_ID, modes1);
+ supportedModesByDisplay.put(DISPLAY_ID_2, modes2);
+
+ Sensor lightSensor = createLightSensor();
+ SensorManager sensorManager = createMockSensorManager(lightSensor);
+ director.start(sensorManager);
+ director.injectSupportedModesByDisplay(supportedModesByDisplay);
+
+ // Disable Force Peak Refresh Rate and Smooth Display
+ setMinRefreshRate(0);
+ setPeakRefreshRate(RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+
+ // Even though the highest refresh rate of the second display == the current min refresh
+ // rate == 60, Force Peak Refresh Rate should remain disabled
+ 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(vote1, /* frameRateLow= */ 0,
+ /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+ assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0,
+ /* frameRateHigh= */ Float.POSITIVE_INFINITY);
+
+ // Even though the highest refresh rate of the second display == the current peak refresh
+ // rate == 60, Smooth Display should remain disabled
+ vote1 = director.getVote(DISPLAY_ID, Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+ vote2 = director.getVote(DISPLAY_ID_2,
+ Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE);
+ assertVoteForRenderFrameRateRange(vote1, /* frameRateLow= */ 0,
+ /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+ assertVoteForRenderFrameRateRange(vote2, /* frameRateLow= */ 0,
+ /* frameRateHigh= */ RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE);
+ }
+
+ @Test
public void testSensorRegistration() {
// First, configure brightness zones or DMD won't register for sensor data.
final FakeDeviceConfig config = mInjector.getDeviceConfig();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
index caa0864..a8b792e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
@@ -213,7 +213,7 @@
any(), any(), any(),
any(), any(),
any(), any(),
- any(),
+ any(), any(),
anyLong(), anyLong());
final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid));
@@ -277,7 +277,7 @@
null, null,
null,
null, null, null,
- null, null,
+ null, null, null,
0, 0);
// Sleep until timeout should have triggered
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
index 9d3caa5..ce281da 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -56,6 +56,7 @@
import org.junit.Rule;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.io.File;
@@ -160,6 +161,7 @@
realAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
realAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal());
+ realAms.mOomAdjuster.mCachedAppOptimizer = Mockito.mock(CachedAppOptimizer.class);
realAms.mOomAdjuster = spy(realAms.mOomAdjuster);
ExtendedMockito.doNothing().when(() -> ProcessList.setOomAdj(anyInt(), anyInt(), anyInt()));
realAms.mPackageManagerInt = mPackageManagerInt;
@@ -283,4 +285,9 @@
receiverList.add(res);
return res;
}
+
+ void setProcessFreezable(ProcessRecord app, boolean pendingFreeze, boolean frozen) {
+ app.mOptRecord.setPendingFreeze(pendingFreeze);
+ app.mOptRecord.setFrozen(frozen);
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index bcf297f..0ba74c6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -42,6 +42,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
@@ -99,7 +100,7 @@
private static final int TEST_UID = android.os.Process.FIRST_APPLICATION_UID;
private static final int TEST_UID2 = android.os.Process.FIRST_APPLICATION_UID + 1;
- @Mock ProcessRecord mProcess;
+ ProcessRecord mProcess;
@Mock BroadcastProcessQueue mQueue1;
@Mock BroadcastProcessQueue mQueue2;
@@ -126,6 +127,9 @@
doReturn(2L).when(mQueue2).getRunnableAt();
doReturn(3L).when(mQueue3).getRunnableAt();
doReturn(4L).when(mQueue4).getRunnableAt();
+
+ final ApplicationInfo ai = makeApplicationInfo(PACKAGE_ORANGE);
+ mProcess = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid));
}
@After
@@ -1409,6 +1413,9 @@
final BroadcastRecord userPresentRecord2 = makeBroadcastRecord(userPresent);
mImpl.enqueueBroadcastLocked(userPresentRecord1);
+ // Wait for a few ms before sending another broadcast to allow comparing the
+ // enqueue timestamps of these broadcasts.
+ SystemClock.sleep(5);
mImpl.enqueueBroadcastLocked(userPresentRecord2);
final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN,
@@ -1475,7 +1482,8 @@
eq(BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST),
eq(BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD),
anyLong(), anyLong(), anyLong(), anyInt(), nullable(String.class),
- anyString(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt()),
+ anyString(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(),
+ anyBoolean(), anyLong()),
times(1));
}
@@ -1746,6 +1754,31 @@
}, false /* andRemove */);
}
+ @Test
+ public void testIsProcessFreezable() throws Exception {
+ final ProcessRecord greenProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN));
+
+ setProcessFreezable(greenProcess, true /* pendingFreeze */, true /* frozen */);
+ mImpl.onProcessFreezableChangedLocked(greenProcess);
+ waitForIdle();
+ assertTrue(mImpl.isProcessFreezable(greenProcess));
+
+ setProcessFreezable(greenProcess, true /* pendingFreeze */, false /* frozen */);
+ mImpl.onProcessFreezableChangedLocked(greenProcess);
+ waitForIdle();
+ assertTrue(mImpl.isProcessFreezable(greenProcess));
+
+ setProcessFreezable(greenProcess, false /* pendingFreeze */, true /* frozen */);
+ mImpl.onProcessFreezableChangedLocked(greenProcess);
+ waitForIdle();
+ assertTrue(mImpl.isProcessFreezable(greenProcess));
+
+ setProcessFreezable(greenProcess, false /* pendingFreeze */, false /* frozen */);
+ mImpl.onProcessFreezableChangedLocked(greenProcess);
+ waitForIdle();
+ assertFalse(mImpl.isProcessFreezable(greenProcess));
+ }
+
// TODO: Reuse BroadcastQueueTest.makeActiveProcessRecord()
private ProcessRecord makeProcessRecord(ApplicationInfo info) {
final ProcessRecord r = spy(new ProcessRecord(mAms, info, info.processName, info.uid));
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 56e5bd6..1172685 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -446,11 +446,6 @@
BackgroundStartPrivileges.NONE, false, null, PROCESS_STATE_UNKNOWN);
}
- private void setProcessFreezable(ProcessRecord app, boolean pendingFreeze, boolean frozen) {
- app.mOptRecord.setPendingFreeze(pendingFreeze);
- app.mOptRecord.setFrozen(frozen);
- }
-
private void assertHealth() {
// If this fails, it'll throw a clear reason message
((BroadcastQueueModernImpl) mQueue).assertHealthLocked();
@@ -2339,6 +2334,40 @@
.isGreaterThan(getReceiverScheduledTime(prioritizedRecord, receiverBlue));
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DEFER_OUTGOING_BCASTS)
+ public void testDeferOutgoingBroadcasts() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ setProcessFreezable(callerApp, true /* pendingFreeze */, false /* frozen */);
+ mQueue.onProcessFreezableChangedLocked(callerApp);
+ waitForIdle();
+
+ final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+ enqueueBroadcast(makeBroadcastRecord(timeTick, callerApp, List.of(
+ makeRegisteredReceiver(receiverGreenApp),
+ makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+ makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW))));
+ // Verify that we invoke the call to freeze the caller app.
+ verify(mAms.mOomAdjuster.mCachedAppOptimizer).freezeAppAsyncImmediateLSP(callerApp);
+
+ waitForIdle();
+ verifyScheduleRegisteredReceiver(never(), receiverGreenApp, timeTick);
+ verifyScheduleReceiver(never(), receiverBlueApp, timeTick);
+ assertNull(mAms.getProcessRecordLocked(PACKAGE_YELLOW, getUidForPackage(PACKAGE_GREEN)));
+
+ setProcessFreezable(callerApp, false /* pendingFreeze */, false /* frozen */);
+ mQueue.onProcessFreezableChangedLocked(callerApp);
+ waitForIdle();
+
+ verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, timeTick);
+ verifyScheduleReceiver(times(1), receiverBlueApp, timeTick);
+ final ProcessRecord receiverYellowApp = mAms.getProcessRecordLocked(PACKAGE_YELLOW,
+ getUidForPackage(PACKAGE_YELLOW));
+ verifyScheduleReceiver(times(1), receiverYellowApp, timeTick);
+ }
+
private long getReceiverScheduledTime(@NonNull BroadcastRecord r, @NonNull Object receiver) {
for (int i = 0; i < r.receivers.size(); ++i) {
if (isReceiverEquals(receiver, r.receivers.get(i))) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
index fcf761f..67be93b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
@@ -215,7 +215,7 @@
any(), any(), any(),
any(), any(),
any(), any(),
- any(),
+ any(), any(),
anyLong(), anyLong());
final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid));
r.setPid(myPid());
@@ -263,7 +263,7 @@
null, null,
null,
null, null, null,
- null, null,
+ null, null, null,
0, 0);
return app;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
index 709a804..97b7af8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
@@ -186,7 +186,7 @@
any());
doReturn(true).when(mAms.mOomAdjuster.mCachedAppOptimizer).useFreezer();
doNothing().when(mAms.mOomAdjuster.mCachedAppOptimizer).freezeAppAsyncInternalLSP(
- any(), anyLong(), anyBoolean());
+ any(), anyLong(), anyBoolean(), anyBoolean());
doReturn(false).when(mAms.mAppProfiler).updateLowMemStateLSP(anyInt(), anyInt(),
anyInt(), anyLong());
@@ -503,7 +503,7 @@
if (clientApp.isFreezable()) {
verify(mAms.mOomAdjuster.mCachedAppOptimizer,
times(Flags.serviceBindingOomAdjPolicy() ? 1 : 0))
- .freezeAppAsyncInternalLSP(eq(clientApp), eq(0L), anyBoolean());
+ .freezeAppAsyncInternalLSP(eq(clientApp), eq(0L), anyBoolean(), anyBoolean());
clearInvocations(mAms.mOomAdjuster.mCachedAppOptimizer);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/PackageManagerBackupAgentTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/PackageManagerBackupAgentTest.java
new file mode 100644
index 0000000..d1b6de0
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/PackageManagerBackupAgentTest.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.backup;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.backup.BackupDataInput;
+import android.app.backup.BackupDataOutput;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedOutputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.nio.ByteBuffer;
+import java.util.Optional;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class PackageManagerBackupAgentTest {
+
+ private static final String EXISTING_PACKAGE_NAME = "com.android.wallpaperbackup";
+ private static final int USER_ID = 0;
+
+ @Rule public TemporaryFolder folder = new TemporaryFolder();
+
+ private PackageManagerBackupAgent mPackageManagerBackupAgent;
+ private ImmutableList<PackageInfo> mPackages;
+ private File mBackupData, mOldState, mNewState;
+
+ @Before
+ public void setUp() throws Exception {
+ PackageManager packageManager = getApplicationContext().getPackageManager();
+
+ PackageInfo existingPackageInfo =
+ packageManager.getPackageInfoAsUser(
+ EXISTING_PACKAGE_NAME, PackageManager.GET_SIGNING_CERTIFICATES, USER_ID);
+ mPackages = ImmutableList.of(existingPackageInfo);
+ mPackageManagerBackupAgent =
+ new PackageManagerBackupAgent(packageManager, mPackages, USER_ID);
+
+ mBackupData = folder.newFile("backup_data");
+ mOldState = folder.newFile("old_state");
+ mNewState = folder.newFile("new_state");
+ }
+
+ @Test
+ public void onBackup_noState_backsUpEverything() throws Exception {
+ // no setup needed
+
+ runBackupAgentOnBackup();
+
+ // key/values should be written to backup data
+ ImmutableMap<String, Optional<ByteBuffer>> keyValues = getKeyValues(mBackupData);
+ assertThat(keyValues.keySet())
+ .containsExactly(
+ PackageManagerBackupAgent.ANCESTRAL_RECORD_KEY,
+ PackageManagerBackupAgent.GLOBAL_METADATA_KEY,
+ EXISTING_PACKAGE_NAME)
+ .inOrder();
+ // new state must not be empty
+ assertThat(mNewState.length()).isGreaterThan(0);
+ }
+
+ @Test
+ public void onBackup_recentState_backsUpNothing() throws Exception {
+ try (ParcelFileDescriptor oldStateDescriptor = openForWriting(mOldState)) {
+ PackageManagerBackupAgent.writeStateFile(mPackages, oldStateDescriptor);
+ }
+
+ runBackupAgentOnBackup();
+
+ // We shouldn't have written anything, but a known issue is that we always write the
+ // ancestral record version.
+ ImmutableMap<String, Optional<ByteBuffer>> keyValues = getKeyValues(mBackupData);
+ assertThat(keyValues.keySet())
+ .containsExactly(PackageManagerBackupAgent.ANCESTRAL_RECORD_KEY);
+ assertThat(mNewState.length()).isGreaterThan(0);
+ assertThat(mNewState.length()).isEqualTo(mOldState.length());
+ }
+
+ @Test
+ public void onBackup_oldState_backsUpChanges() throws Exception {
+ String uninstalledPackageName = "does.not.exist";
+ try (ParcelFileDescriptor oldStateDescriptor = openForWriting(mOldState)) {
+ PackageManagerBackupAgent.writeStateFile(
+ ImmutableList.of(createPackage(uninstalledPackageName, 1)), oldStateDescriptor);
+ }
+
+ runBackupAgentOnBackup();
+
+ // Note that uninstalledPackageName should not exist, i.e. it did not get deleted.
+ ImmutableMap<String, Optional<ByteBuffer>> keyValues = getKeyValues(mBackupData);
+ assertThat(keyValues.keySet())
+ .containsExactly(
+ PackageManagerBackupAgent.ANCESTRAL_RECORD_KEY, EXISTING_PACKAGE_NAME);
+ assertThat(mNewState.length()).isGreaterThan(0);
+ }
+
+ @Test
+ public void onBackup_legacyState_backsUpEverything() throws Exception {
+ String uninstalledPackageName = "does.not.exist";
+ writeLegacyStateFile(
+ mOldState,
+ ImmutableList.of(createPackage(uninstalledPackageName, 1), mPackages.getFirst()));
+
+ runBackupAgentOnBackup();
+
+ ImmutableMap<String, Optional<ByteBuffer>> keyValues = getKeyValues(mBackupData);
+ assertThat(keyValues.keySet())
+ .containsExactly(
+ PackageManagerBackupAgent.ANCESTRAL_RECORD_KEY,
+ PackageManagerBackupAgent.GLOBAL_METADATA_KEY,
+ EXISTING_PACKAGE_NAME);
+ assertThat(mNewState.length()).isGreaterThan(0);
+ }
+
+ @Test
+ public void onRestore_recentBackup_restoresBackup() throws Exception {
+ runBackupAgentOnBackup();
+
+ runBackupAgentOnRestore();
+
+ assertThat(mPackageManagerBackupAgent.getRestoredPackages())
+ .containsExactly(EXISTING_PACKAGE_NAME);
+ // onRestore does not write to newState
+ assertThat(mNewState.length()).isEqualTo(0);
+ }
+
+ @Test
+ public void onRestore_legacyBackup_restoresBackup() throws Exception {
+ // A legacy backup is one without an ancestral record version. Ancestral record versions
+ // are always written however, so we'll need to delete it from the backup data before
+ // restoring.
+ runBackupAgentOnBackup();
+ deleteKeyFromBackupData(mBackupData, PackageManagerBackupAgent.ANCESTRAL_RECORD_KEY);
+
+ runBackupAgentOnRestore();
+
+ assertThat(mPackageManagerBackupAgent.getRestoredPackages())
+ .containsExactly(EXISTING_PACKAGE_NAME);
+ // onRestore does not write to newState
+ assertThat(mNewState.length()).isEqualTo(0);
+ }
+
+ private void runBackupAgentOnBackup() throws Exception {
+ try (ParcelFileDescriptor oldStateDescriptor = openForReading(mOldState);
+ ParcelFileDescriptor backupDataDescriptor = openForWriting(mBackupData);
+ ParcelFileDescriptor newStateDescriptor = openForWriting(mNewState)) {
+ mPackageManagerBackupAgent.onBackup(
+ oldStateDescriptor,
+ new BackupDataOutput(backupDataDescriptor.getFileDescriptor()),
+ newStateDescriptor);
+ }
+ }
+
+ private void runBackupAgentOnRestore() throws Exception {
+ try (ParcelFileDescriptor backupDataDescriptor = openForReading(mBackupData);
+ ParcelFileDescriptor newStateDescriptor = openForWriting(mNewState)) {
+ mPackageManagerBackupAgent.onRestore(
+ new BackupDataInput(backupDataDescriptor.getFileDescriptor()),
+ /* appVersionCode= */ 0,
+ newStateDescriptor);
+ }
+ }
+
+ private void deleteKeyFromBackupData(File backupData, String key) throws Exception {
+ File temporaryBackupData = folder.newFile("backup_data.tmp");
+ try (ParcelFileDescriptor inputDescriptor = openForReading(backupData);
+ ParcelFileDescriptor outputDescriptor = openForWriting(temporaryBackupData); ) {
+ BackupDataInput input = new BackupDataInput(inputDescriptor.getFileDescriptor());
+ BackupDataOutput output = new BackupDataOutput(outputDescriptor.getFileDescriptor());
+ while (input.readNextHeader()) {
+ if (input.getKey().equals(key)) {
+ if (input.getDataSize() > 0) {
+ input.skipEntityData();
+ }
+ continue;
+ }
+ output.writeEntityHeader(input.getKey(), input.getDataSize());
+ if (input.getDataSize() < 0) {
+ input.skipEntityData();
+ } else {
+ byte[] buf = new byte[input.getDataSize()];
+ input.readEntityData(buf, 0, buf.length);
+ output.writeEntityData(buf, buf.length);
+ }
+ }
+ }
+ assertThat(temporaryBackupData.renameTo(backupData)).isTrue();
+ }
+
+ private static PackageInfo createPackage(String name, int versionCode) {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = name;
+ packageInfo.versionCodeMajor = versionCode;
+ return packageInfo;
+ }
+
+ /** This creates a legacy state file in which {@code STATE_FILE_HEADER} was not yet present. */
+ private static void writeLegacyStateFile(File stateFile, ImmutableList<PackageInfo> packages)
+ throws Exception {
+ try (ParcelFileDescriptor stateFileDescriptor = openForWriting(stateFile);
+ DataOutputStream out =
+ new DataOutputStream(
+ new BufferedOutputStream(
+ new FileOutputStream(
+ stateFileDescriptor.getFileDescriptor())))) {
+ out.writeUTF(PackageManagerBackupAgent.GLOBAL_METADATA_KEY);
+ out.writeInt(Build.VERSION.SDK_INT);
+ out.writeUTF(Build.VERSION.INCREMENTAL);
+
+ // now write all the app names + versions
+ for (PackageInfo pkg : packages) {
+ out.writeUTF(pkg.packageName);
+ out.writeInt(pkg.versionCode);
+ }
+ out.flush();
+ }
+ }
+
+ /**
+ * Reads the given backup data file and returns a map of key-value pairs. The value is a {@link
+ * ByteBuffer} wrapped in an {@link Optional}, where the empty {@link Optional} represents a key
+ * deletion.
+ */
+ private static ImmutableMap<String, Optional<ByteBuffer>> getKeyValues(File backupData)
+ throws Exception {
+ ImmutableMap.Builder<String, Optional<ByteBuffer>> builder = ImmutableMap.builder();
+ try (ParcelFileDescriptor backupDataDescriptor = openForReading(backupData)) {
+ BackupDataInput backupDataInput =
+ new BackupDataInput(backupDataDescriptor.getFileDescriptor());
+ while (backupDataInput.readNextHeader()) {
+ ByteBuffer value = null;
+ if (backupDataInput.getDataSize() >= 0) {
+ byte[] val = new byte[backupDataInput.getDataSize()];
+ backupDataInput.readEntityData(val, 0, val.length);
+ value = ByteBuffer.wrap(val);
+ }
+ builder.put(backupDataInput.getKey(), Optional.ofNullable(value));
+ }
+ }
+ return builder.build();
+ }
+
+ private static ParcelFileDescriptor openForWriting(File file) throws Exception {
+ return ParcelFileDescriptor.open(
+ file,
+ ParcelFileDescriptor.MODE_CREATE
+ | ParcelFileDescriptor.MODE_TRUNCATE
+ | ParcelFileDescriptor.MODE_WRITE_ONLY);
+ }
+
+ private static ParcelFileDescriptor openForReading(File file) throws Exception {
+ return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS b/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS
index daa0211..8337fd2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS
@@ -1,3 +1 @@
-ancr@google.com
-harshitmahajan@google.com
-robertogil@google.com
+include /services/core/java/com/android/server/crashrecovery/OWNERS
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
index 6ac56bf..4ac4484 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
@@ -1,5 +1,5 @@
{
- "postsubmit": [
+ "presubmit": [
{
"name": "RollbackPackageHealthObserverTests",
"options": [
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index e168596..16d05b1 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -214,7 +214,7 @@
.thenReturn(mA11yWindowInfos.get(0));
when(mMockA11yWindowManager.findA11yWindowInfoByIdLocked(PIP_WINDOWID))
.thenReturn(mA11yWindowInfos.get(1));
- when(mMockA11yWindowManager.getDisplayIdByUserIdAndWindowIdLocked(USER_ID,
+ when(mMockA11yWindowManager.getDisplayIdByUserIdAndWindowId(USER_ID,
WINDOWID_ONSECONDDISPLAY)).thenReturn(SECONDARY_DISPLAY_ID);
when(mMockA11yWindowManager.getWindowListLocked(SECONDARY_DISPLAY_ID))
.thenReturn(mA11yWindowInfosOnSecondDisplay);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 95cfc2a..7f88b00 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -61,29 +61,38 @@
import android.graphics.drawable.Icon;
import android.hardware.display.DisplayManagerGlobal;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
import android.os.LocaleList;
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.provider.Settings;
+import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
+import android.testing.TestableLooper;
import android.util.ArraySet;
import android.view.Display;
import android.view.DisplayAdjustments;
import android.view.DisplayInfo;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityWindowAttributes;
+import android.view.accessibility.IAccessibilityManager;
-import androidx.test.InstrumentationRegistry;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import com.android.compatibility.common.util.TestUtils;
import com.android.internal.R;
+import com.android.internal.accessibility.common.ShortcutConstants;
+import com.android.internal.accessibility.common.ShortcutConstants.FloatingMenuSize;
+import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
+import com.android.internal.accessibility.util.AccessibilityUtils;
+import com.android.internal.accessibility.util.ShortcutUtils;
import com.android.internal.compat.IPlatformCompat;
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityManagerService.AccessibilityDisplayListener;
@@ -91,31 +100,38 @@
import com.android.server.accessibility.magnification.MagnificationConnectionManager;
import com.android.server.accessibility.magnification.MagnificationController;
import com.android.server.accessibility.magnification.MagnificationProcessor;
-import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.pm.UserManagerInternal;
+import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
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.Mockito;
import org.mockito.MockitoAnnotations;
+import org.mockito.internal.util.reflection.FieldReader;
+import org.mockito.internal.util.reflection.FieldSetter;
import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
/**
* APCT tests for {@link AccessibilityManagerService}.
*/
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
public class AccessibilityManagerServiceTest {
@Rule
public final A11yTestableContext mTestableContext = new A11yTestableContext(
@@ -140,6 +156,12 @@
TEST_PENDING_INTENT);
private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY + 1;
+ private static final String TARGET_MAGNIFICATION = MAGNIFICATION_CONTROLLER_NAME;
+ private static final ComponentName TARGET_ALWAYS_ON_A11Y_SERVICE =
+ new ComponentName("FakePackage", "AlwaysOnA11yService");
+ private static final String TARGET_ALWAYS_ON_A11Y_SERVICE_TILE_CLASS = "TileService";
+ private static final ComponentName TARGET_STANDARD_A11Y_SERVICE =
+ new ComponentName("FakePackage", "StandardA11yService");
static final ComponentName COMPONENT_NAME = new ComponentName(
"com.android.server.accessibility", "AccessibilityManagerServiceTest");
@@ -163,24 +185,33 @@
@Mock private MagnificationController mMockMagnificationController;
@Mock private FullScreenMagnificationController mMockFullScreenMagnificationController;
@Mock private ProxyManager mProxyManager;
+ @Mock private StatusBarManagerInternal mStatusBarManagerInternal;
@Captor private ArgumentCaptor<Intent> mIntentArgumentCaptor;
- private MessageCapturingHandler mHandler = new MessageCapturingHandler(null);
+ private IAccessibilityManager mA11yManagerServiceOnDevice;
private AccessibilityServiceConnection mAccessibilityServiceConnection;
private AccessibilityInputFilter mInputFilter;
private AccessibilityManagerService mA11yms;
+ private TestableLooper mTestableLooper;
+ private Handler mHandler;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ mTestableLooper = TestableLooper.get(this);
+ mHandler = new Handler(mTestableLooper.getLooper());
+
LocalServices.removeServiceForTest(WindowManagerInternal.class);
LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
LocalServices.removeServiceForTest(UserManagerInternal.class);
+ LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
LocalServices.addService(
WindowManagerInternal.class, mMockWindowManagerService);
LocalServices.addService(
ActivityTaskManagerInternal.class, mMockActivityTaskManagerInternal);
LocalServices.addService(
UserManagerInternal.class, mMockUserManagerInternal);
+ LocalServices.addService(
+ StatusBarManagerInternal.class, mStatusBarManagerInternal);
mInputFilter = Mockito.mock(FakeInputFilter.class);
when(mMockMagnificationController.getMagnificationConnectionManager()).thenReturn(
@@ -218,6 +249,19 @@
final AccessibilityUserState userState = new AccessibilityUserState(
mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
mA11yms.mUserStates.put(mA11yms.getCurrentUserIdLocked(), userState);
+ AccessibilityManager am = mTestableContext.getSystemService(AccessibilityManager.class);
+ mA11yManagerServiceOnDevice = (IAccessibilityManager) new FieldReader(am,
+ AccessibilityManager.class.getDeclaredField("mService")).read();
+ FieldSetter.setField(am, AccessibilityManager.class.getDeclaredField("mService"), mA11yms);
+ }
+
+ @After
+ public void cleanUp() throws Exception {
+ mTestableLooper.processAllMessages();
+ AccessibilityManager am = mTestableContext.getSystemService(AccessibilityManager.class);
+ FieldSetter.setField(
+ am, AccessibilityManager.class.getDeclaredField("mService"),
+ mA11yManagerServiceOnDevice);
}
private void setupAccessibilityServiceConnection(int serviceInfoFlag) {
@@ -297,7 +341,8 @@
mA11yms.getCurrentUserIdLocked());
mA11yms.notifySystemActionsChangedLocked(userState);
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ mTestableLooper.processAllMessages();
+
verify(mMockServiceClient).onSystemActionsChanged();
}
@@ -415,7 +460,7 @@
);
mA11yms.onMagnificationTransitionEndedLocked(Display.DEFAULT_DISPLAY, true);
- mHandler.sendAllMessages();
+ mTestableLooper.processAllMessages();
ArgumentCaptor<Display> displayCaptor = ArgumentCaptor.forClass(Display.class);
verify(mInputFilter, timeout(100)).refreshMagnificationMode(displayCaptor.capture());
@@ -730,7 +775,7 @@
mA11yms.performAccessibilityShortcut(
ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
- mHandler.sendAllMessages();
+ mTestableLooper.processAllMessages();
assertStartActivityWithExpectedComponentName(mTestableContext.getMockContext(),
ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
@@ -805,26 +850,6 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK)
- // Test old behavior to validate lock detection for the old (locked access) case.
- public void testPackageMonitorScanPackages_scansWhileHoldingLock() {
- setupAccessibilityServiceConnection(0);
- final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning();
- when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
- .thenReturn(List.of(mMockResolveInfo));
- when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true);
-
- final Intent packageIntent = new Intent(Intent.ACTION_PACKAGE_ADDED);
- packageIntent.setData(Uri.parse("test://package"));
- packageIntent.putExtra(Intent.EXTRA_USER_HANDLE, mA11yms.getCurrentUserIdLocked());
- packageIntent.putExtra(Intent.EXTRA_REPLACING, true);
- mA11yms.getPackageMonitor().doHandlePackageEvent(packageIntent);
-
- assertThat(lockState.get()).containsExactly(true);
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK)
public void testPackageMonitorScanPackages_scansWithoutHoldingLock() {
setupAccessibilityServiceConnection(0);
final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning();
@@ -842,7 +867,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK)
public void testSwitchUserScanPackages_scansWithoutHoldingLock() {
setupAccessibilityServiceConnection(0);
final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning();
@@ -907,11 +931,14 @@
public void testIsAccessibilityServiceWarningRequired_notRequiredIfAllowlisted() {
mockManageAccessibilityGranted(mTestableContext);
final AccessibilityServiceInfo info_a = mockAccessibilityServiceInfo(
- new ComponentName("package_a", "class_a"), true);
+ new ComponentName("package_a", "class_a"),
+ /* isSystemApp= */ true, /* isAlwaysOnService= */ false);
final AccessibilityServiceInfo info_b = mockAccessibilityServiceInfo(
- new ComponentName("package_b", "class_b"), false);
+ new ComponentName("package_b", "class_b"),
+ /* isSystemApp= */ false, /* isAlwaysOnService= */ false);
final AccessibilityServiceInfo info_c = mockAccessibilityServiceInfo(
- new ComponentName("package_c", "class_c"), true);
+ new ComponentName("package_c", "class_c"),
+ /* isSystemApp= */ true, /* isAlwaysOnService= */ false);
mTestableContext.getOrCreateTestableResources().addOverride(
R.array.config_trustedAccessibilityServices,
new String[]{
@@ -926,14 +953,380 @@
assertThat(mA11yms.isAccessibilityServiceWarningRequired(info_c)).isFalse();
}
+ @Test
+ public void enableShortcutsForTargets_permissionNotGranted_throwsException() {
+ mTestableContext.getTestablePermissions().setPermission(
+ Manifest.permission.MANAGE_ACCESSIBILITY, PackageManager.PERMISSION_DENIED);
+
+ assertThrows(SecurityException.class,
+ () -> mA11yms.enableShortcutsForTargets(
+ /* enable= */true,
+ UserShortcutType.SOFTWARE,
+ List.of(TARGET_MAGNIFICATION),
+ mA11yms.getCurrentUserIdLocked()));
+ }
+
+ @Test
+ public void enableShortcutsForTargets_enableSoftwareShortcut_shortcutTurnedOn()
+ throws Exception {
+ mockManageAccessibilityGranted(mTestableContext);
+ setupShortcutTargetServices();
+ String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
+
+ mA11yms.enableShortcutsForTargets(
+ /* enable= */ true,
+ UserShortcutType.SOFTWARE,
+ List.of(target),
+ mA11yms.getCurrentUserIdLocked());
+ mTestableLooper.processAllMessages();
+
+ assertThat(ShortcutUtils.isComponentIdExistingInSettings(
+ mTestableContext, UserShortcutType.SOFTWARE, target
+ )).isTrue();
+ }
+
+ @Test
+ public void enableShortcutsForTargets_disableSoftwareShortcut_shortcutTurnedOff()
+ throws Exception {
+ String target = TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString();
+ enableShortcutsForTargets_enableSoftwareShortcut_shortcutTurnedOn();
+
+ mA11yms.enableShortcutsForTargets(
+ /* enable= */ false,
+ UserShortcutType.SOFTWARE,
+ List.of(target),
+ mA11yms.getCurrentUserIdLocked());
+ mTestableLooper.processAllMessages();
+
+ assertThat(ShortcutUtils.isComponentIdExistingInSettings(
+ mTestableContext, UserShortcutType.SOFTWARE, target
+ )).isFalse();
+ }
+
+ @Test
+ public void enableShortcutsForTargets_enableSoftwareShortcutWithMagnification_menuSizeIncreased() {
+ mockManageAccessibilityGranted(mTestableContext);
+
+ mA11yms.enableShortcutsForTargets(
+ /* enable= */ true,
+ UserShortcutType.SOFTWARE,
+ List.of(MAGNIFICATION_CONTROLLER_NAME),
+ mA11yms.getCurrentUserIdLocked());
+ mTestableLooper.processAllMessages();
+
+ assertThat(
+ Settings.Secure.getInt(
+ mTestableContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
+ FloatingMenuSize.UNKNOWN))
+ .isEqualTo(FloatingMenuSize.LARGE);
+ }
+
+ @Test
+ public void enableShortcutsForTargets_enableSoftwareShortcutWithMagnification_userConfigureSmallMenuSize_menuSizeNotChanged() {
+ mockManageAccessibilityGranted(mTestableContext);
+ Settings.Secure.putInt(
+ mTestableContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
+ FloatingMenuSize.SMALL);
+
+ mA11yms.enableShortcutsForTargets(
+ /* enable= */ true,
+ UserShortcutType.SOFTWARE,
+ List.of(MAGNIFICATION_CONTROLLER_NAME),
+ mA11yms.getCurrentUserIdLocked());
+ mTestableLooper.processAllMessages();
+
+ assertThat(
+ Settings.Secure.getInt(
+ mTestableContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
+ FloatingMenuSize.UNKNOWN))
+ .isEqualTo(FloatingMenuSize.SMALL);
+ }
+
+ @Test
+ public void enableShortcutsForTargets_enableAlwaysOnServiceSoftwareShortcut_turnsOnAlwaysOnService()
+ throws Exception {
+ mockManageAccessibilityGranted(mTestableContext);
+ setupShortcutTargetServices();
+
+ mA11yms.enableShortcutsForTargets(
+ /* enable= */ true,
+ UserShortcutType.SOFTWARE,
+ List.of(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString()),
+ mA11yms.getCurrentUserIdLocked());
+ mTestableLooper.processAllMessages();
+
+ assertThat(
+ AccessibilityUtils.getEnabledServicesFromSettings(
+ mTestableContext,
+ mA11yms.getCurrentUserIdLocked())
+ ).contains(TARGET_ALWAYS_ON_A11Y_SERVICE);
+ }
+
+ @Test
+ public void enableShortcutsForTargets_disableAlwaysOnServiceSoftwareShortcut_turnsOffAlwaysOnService()
+ throws Exception {
+ enableShortcutsForTargets_enableAlwaysOnServiceSoftwareShortcut_turnsOnAlwaysOnService();
+
+ mA11yms.enableShortcutsForTargets(
+ /* enable= */ false,
+ UserShortcutType.SOFTWARE,
+ List.of(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString()),
+ mA11yms.getCurrentUserIdLocked());
+ mTestableLooper.processAllMessages();
+
+ assertThat(
+ AccessibilityUtils.getEnabledServicesFromSettings(
+ mTestableContext,
+ mA11yms.getCurrentUserIdLocked())
+ ).doesNotContain(TARGET_ALWAYS_ON_A11Y_SERVICE);
+ }
+
+ @Test
+ public void enableShortcutsForTargets_enableStandardServiceSoftwareShortcut_wontTurnOnService()
+ throws Exception {
+ mockManageAccessibilityGranted(mTestableContext);
+ setupShortcutTargetServices();
+
+ mA11yms.enableShortcutsForTargets(
+ /* enable= */ true,
+ UserShortcutType.SOFTWARE,
+ List.of(TARGET_STANDARD_A11Y_SERVICE.flattenToString()),
+ mA11yms.getCurrentUserIdLocked());
+ mTestableLooper.processAllMessages();
+
+ assertThat(
+ AccessibilityUtils.getEnabledServicesFromSettings(
+ mTestableContext,
+ mA11yms.getCurrentUserIdLocked())
+ ).doesNotContain(TARGET_STANDARD_A11Y_SERVICE);
+ }
+
+ @Test
+ public void enableShortcutsForTargets_disableStandardServiceSoftwareShortcutWithServiceOn_wontTurnOffService()
+ throws Exception {
+ enableShortcutsForTargets_enableStandardServiceSoftwareShortcut_wontTurnOnService();
+ AccessibilityUtils.setAccessibilityServiceState(
+ mTestableContext, TARGET_STANDARD_A11Y_SERVICE, /* enabled= */ true);
+
+ mA11yms.enableShortcutsForTargets(
+ /* enable= */ false,
+ UserShortcutType.SOFTWARE,
+ List.of(TARGET_STANDARD_A11Y_SERVICE.flattenToString()),
+ mA11yms.getCurrentUserIdLocked());
+ mTestableLooper.processAllMessages();
+
+ assertThat(
+ AccessibilityUtils.getEnabledServicesFromSettings(
+ mTestableContext,
+ mA11yms.getCurrentUserIdLocked())
+ ).contains(TARGET_STANDARD_A11Y_SERVICE);
+ }
+
+ @Test
+ public void enableShortcutsForTargets_enableTripleTapShortcut_settingUpdated() {
+ mockManageAccessibilityGranted(mTestableContext);
+
+ mA11yms.enableShortcutsForTargets(
+ /* enable= */ true,
+ UserShortcutType.TRIPLETAP,
+ List.of(TARGET_MAGNIFICATION),
+ mA11yms.getCurrentUserIdLocked());
+ mTestableLooper.processAllMessages();
+
+ assertThat(
+ Settings.Secure.getInt(
+ mTestableContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
+ AccessibilityUtils.State.OFF)
+ ).isEqualTo(AccessibilityUtils.State.ON);
+ }
+
+ @Test
+ public void enableShortcutsForTargets_disableTripleTapShortcut_settingUpdated() {
+ enableShortcutsForTargets_enableTripleTapShortcut_settingUpdated();
+
+ mA11yms.enableShortcutsForTargets(
+ /* enable= */ false,
+ UserShortcutType.TRIPLETAP,
+ List.of(TARGET_MAGNIFICATION),
+ mA11yms.getCurrentUserIdLocked());
+
+ assertThat(
+ Settings.Secure.getInt(
+ mTestableContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
+ AccessibilityUtils.State.OFF)
+ ).isEqualTo(AccessibilityUtils.State.OFF);
+ }
+
+ @Test
+ public void enableShortcutsForTargets_enableMultiFingerMultiTapsShortcut_settingUpdated() {
+ mockManageAccessibilityGranted(mTestableContext);
+
+ mA11yms.enableShortcutsForTargets(
+ /* enable= */ true,
+ UserShortcutType.TWOFINGER_DOUBLETAP,
+ List.of(TARGET_MAGNIFICATION),
+ mA11yms.getCurrentUserIdLocked());
+ mTestableLooper.processAllMessages();
+
+ assertThat(
+ Settings.Secure.getInt(
+ mTestableContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
+ AccessibilityUtils.State.OFF)
+ ).isEqualTo(AccessibilityUtils.State.ON);
+ }
+
+ @Test
+ public void enableShortcutsForTargets_disableMultiFingerMultiTapsShortcut_settingUpdated() {
+ enableShortcutsForTargets_enableMultiFingerMultiTapsShortcut_settingUpdated();
+
+ mA11yms.enableShortcutsForTargets(
+ /* enable= */ false,
+ UserShortcutType.TWOFINGER_DOUBLETAP,
+ List.of(TARGET_MAGNIFICATION),
+ mA11yms.getCurrentUserIdLocked());
+ mTestableLooper.processAllMessages();
+
+ assertThat(
+ Settings.Secure.getInt(
+ mTestableContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED,
+ AccessibilityUtils.State.OFF)
+ ).isEqualTo(AccessibilityUtils.State.OFF);
+ }
+
+ @Test
+ public void enableShortcutsForTargets_enableVolumeKeysShortcut_shortcutSet() {
+ mockManageAccessibilityGranted(mTestableContext);
+ setupShortcutTargetServices();
+
+ mA11yms.enableShortcutsForTargets(
+ /* enable= */ true,
+ UserShortcutType.HARDWARE,
+ List.of(TARGET_STANDARD_A11Y_SERVICE.flattenToString()),
+ mA11yms.getCurrentUserIdLocked());
+ mTestableLooper.processAllMessages();
+
+ assertThat(
+ ShortcutUtils.isComponentIdExistingInSettings(
+ mTestableContext, ShortcutConstants.UserShortcutType.HARDWARE,
+ TARGET_STANDARD_A11Y_SERVICE.flattenToString())
+ ).isTrue();
+ }
+
+ @Test
+ public void enableShortcutsForTargets_disableVolumeKeysShortcut_shortcutNotSet() {
+ enableShortcutsForTargets_enableVolumeKeysShortcut_shortcutSet();
+
+ mA11yms.enableShortcutsForTargets(
+ /* enable= */ false,
+ UserShortcutType.HARDWARE,
+ List.of(TARGET_STANDARD_A11Y_SERVICE.flattenToString()),
+ mA11yms.getCurrentUserIdLocked());
+ mTestableLooper.processAllMessages();
+
+ assertThat(
+ ShortcutUtils.isComponentIdExistingInSettings(
+ mTestableContext, ShortcutConstants.UserShortcutType.HARDWARE,
+ TARGET_STANDARD_A11Y_SERVICE.flattenToString())
+ ).isFalse();
+ }
+
+ @Test
+ public void enableShortcutsForTargets_enableQuickSettings_shortcutSet() {
+ mockManageAccessibilityGranted(mTestableContext);
+ setupShortcutTargetServices();
+
+ mA11yms.enableShortcutsForTargets(
+ /* enable= */ true,
+ UserShortcutType.QUICK_SETTINGS,
+ List.of(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString()),
+ mA11yms.getCurrentUserIdLocked());
+ mTestableLooper.processAllMessages();
+
+ assertThat(
+ ShortcutUtils.isComponentIdExistingInSettings(
+ mTestableContext, UserShortcutType.QUICK_SETTINGS,
+ TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString())
+ ).isTrue();
+ verify(mStatusBarManagerInternal)
+ .addQsTileToFrontOrEnd(
+ new ComponentName(
+ TARGET_ALWAYS_ON_A11Y_SERVICE.getPackageName(),
+ TARGET_ALWAYS_ON_A11Y_SERVICE_TILE_CLASS),
+ /* end= */ true);
+ }
+
+ @Test
+ public void enableShortcutsForTargets_disableQuickSettings_shortcutNotSet() {
+ enableShortcutsForTargets_enableQuickSettings_shortcutSet();
+
+ mA11yms.enableShortcutsForTargets(
+ /* enable= */ false,
+ UserShortcutType.QUICK_SETTINGS,
+ List.of(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString()),
+ mA11yms.getCurrentUserIdLocked());
+ mTestableLooper.processAllMessages();
+
+ assertThat(
+ ShortcutUtils.isComponentIdExistingInSettings(
+ mTestableContext, UserShortcutType.QUICK_SETTINGS,
+ TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString())
+ ).isFalse();
+ verify(mStatusBarManagerInternal)
+ .removeQsTile(
+ new ComponentName(
+ TARGET_ALWAYS_ON_A11Y_SERVICE.getPackageName(),
+ TARGET_ALWAYS_ON_A11Y_SERVICE_TILE_CLASS));
+ }
+
+ @Test
+ public void getA11yFeatureToTileMap_permissionNotGranted_throwsException() {
+ mTestableContext.getTestablePermissions().setPermission(
+ Manifest.permission.MANAGE_ACCESSIBILITY, PackageManager.PERMISSION_DENIED);
+
+ assertThrows(SecurityException.class,
+ () -> mA11yms.getA11yFeatureToTileMap(mA11yms.getCurrentUserIdLocked()));
+ }
+
+ @Test
+ public void getA11yFeatureToTileMap() {
+ mockManageAccessibilityGranted(mTestableContext);
+ setupShortcutTargetServices();
+
+ Bundle bundle = mA11yms.getA11yFeatureToTileMap(mA11yms.getCurrentUserIdLocked());
+
+ // Framework tile size + TARGET_ALWAYS_ON_A11Y_SERVICE_TILE_CLASS
+ assertThat(bundle.size())
+ .isEqualTo(ShortcutConstants.A11Y_FEATURE_TO_FRAMEWORK_TILE.size() + 1);
+ assertThat(
+ bundle.getParcelable(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString(),
+ ComponentName.class)
+ ).isEqualTo(
+ new ComponentName(
+ TARGET_ALWAYS_ON_A11Y_SERVICE.getPackageName(),
+ TARGET_ALWAYS_ON_A11Y_SERVICE_TILE_CLASS));
+ for (Map.Entry<ComponentName, ComponentName> entry :
+ ShortcutConstants.A11Y_FEATURE_TO_FRAMEWORK_TILE.entrySet()) {
+ assertThat(bundle.getParcelable(entry.getKey().flattenToString(), ComponentName.class))
+ .isEqualTo(entry.getValue());
+ }
+ }
+
private static AccessibilityServiceInfo mockAccessibilityServiceInfo(
ComponentName componentName) {
- return mockAccessibilityServiceInfo(componentName, false);
+ return mockAccessibilityServiceInfo(
+ componentName, /* isSystemApp= */ false, /* isAlwaysOnService=*/ false);
}
private static AccessibilityServiceInfo mockAccessibilityServiceInfo(
ComponentName componentName,
- boolean isSystemApp) {
+ boolean isSystemApp, boolean isAlwaysOnService) {
AccessibilityServiceInfo accessibilityServiceInfo =
Mockito.spy(new AccessibilityServiceInfo());
accessibilityServiceInfo.setComponentName(componentName);
@@ -941,7 +1334,15 @@
when(accessibilityServiceInfo.getResolveInfo()).thenReturn(mockResolveInfo);
mockResolveInfo.serviceInfo = Mockito.mock(ServiceInfo.class);
mockResolveInfo.serviceInfo.applicationInfo = Mockito.mock(ApplicationInfo.class);
+ mockResolveInfo.serviceInfo.packageName = componentName.getPackageName();
+ mockResolveInfo.serviceInfo.name = componentName.getClassName();
when(mockResolveInfo.serviceInfo.applicationInfo.isSystemApp()).thenReturn(isSystemApp);
+ if (isAlwaysOnService) {
+ accessibilityServiceInfo.flags |=
+ AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
+ mockResolveInfo.serviceInfo.applicationInfo.targetSdkVersion =
+ Build.VERSION_CODES.R;
+ }
return accessibilityServiceInfo;
}
@@ -974,6 +1375,22 @@
Intent.EXTRA_COMPONENT_NAME)).isEqualTo(componentName);
}
+ private void setupShortcutTargetServices() {
+ AccessibilityServiceInfo alwaysOnServiceInfo = mockAccessibilityServiceInfo(
+ TARGET_ALWAYS_ON_A11Y_SERVICE,
+ /* isSystemApp= */ false,
+ /* isAlwaysOnService= */ true);
+ when(alwaysOnServiceInfo.getTileServiceName())
+ .thenReturn(TARGET_ALWAYS_ON_A11Y_SERVICE_TILE_CLASS);
+ AccessibilityServiceInfo standardServiceInfo = mockAccessibilityServiceInfo(
+ TARGET_STANDARD_A11Y_SERVICE,
+ /* isSystemApp= */ false,
+ /* isAlwaysOnService= */ false);
+ mA11yms.getCurrentUserState().mInstalledServices.addAll(
+ List.of(alwaysOnServiceInfo, standardServiceInfo));
+ mA11yms.getCurrentUserState().updateTileServiceMapForAccessibilityServiceLocked();
+ }
+
public static class FakeInputFilter extends AccessibilityInputFilter {
FakeInputFilter(Context context,
AccessibilityManagerService service) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
index dc26e6e..f6dc2f0 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
@@ -18,21 +18,23 @@
import static com.android.server.accessibility.AbstractAccessibilityServiceConnection.DISPLAY_TYPE_DEFAULT;
import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.DisplayIdMatcher.displayId;
+import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowIdMatcher.windowId;
import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowChangesMatcher.a11yWindowChanges;
-import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowIdMatcher.a11yWindowId;
+import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.EventWindowIdMatcher.eventWindowId;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
@@ -350,6 +352,88 @@
}
@Test
+ public void onWindowsChanged_shouldNotReportNonTouchableWindow() {
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ when(window.isTouchable()).thenReturn(false);
+ final int windowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, window.getWindowInfo().token);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, not(hasItem(windowId(windowId))));
+ }
+
+ @Test
+ public void onWindowsChanged_shouldReportFocusedNonTouchableWindow() {
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX);
+ when(window.isTouchable()).thenReturn(false);
+ final int windowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, window.getWindowInfo().token);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasItem(windowId(windowId)));
+ }
+
+ @Test
+ public void onWindowsChanged_trustedFocusedNonTouchableWindow_shouldNotHideWindowsBelow() {
+ // Make the focused trusted un-touchable window fullscreen.
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX);
+ setRegionForMockAccessibilityWindow(window, new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+ when(window.isTouchable()).thenReturn(false);
+ when(window.isTrustedOverlay()).thenReturn(true);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(NUM_OF_WINDOWS));
+ }
+
+ @Test
+ public void onWindowsChanged_accessibilityOverlay_shouldNotHideWindowsBelow() {
+ // Make the a11y overlay window fullscreen.
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(window, new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+ when(window.getType()).thenReturn(WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(NUM_OF_WINDOWS));
+ }
+
+ @Test
+ public void onWindowsChanged_shouldReportFocusedWindowEvenIfOccluded() {
+ // Make the front window fullscreen.
+ final AccessibilityWindow frontWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(frontWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+ final int frontWindowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, frontWindow.getWindowInfo().token);
+
+ final AccessibilityWindow focusedWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX);
+ final int focusedWindowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, focusedWindow.getWindowInfo().token);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(2));
+ assertThat(a11yWindows.get(0), windowId(frontWindowId));
+ assertThat(a11yWindows.get(1), windowId(focusedWindowId));
+ }
+
+ @Test
public void onWindowsChangedAndForceSend_shouldUpdateWindows() {
assertNotEquals("new title",
toString(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)
@@ -631,11 +715,11 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(currentActiveWindowId),
+ eventWindowId(currentActiveWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
assertThat(captor.getAllValues().get(1),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
}
@@ -661,7 +745,7 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(
AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
}
@@ -710,12 +794,12 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(initialDisplayId),
- a11yWindowId(initialWindowId),
+ eventWindowId(initialWindowId),
a11yWindowChanges(
AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
assertThat(captor.getAllValues().get(1),
allOf(displayId(eventDisplayId),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(
AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
}
@@ -771,11 +855,11 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
assertThat(captor.getAllValues().get(1),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(currentActiveWindowId),
+ eventWindowId(currentActiveWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
}
@@ -979,7 +1063,7 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(windowId),
+ eventWindowId(windowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_REMOVED)));
}
@@ -1001,7 +1085,7 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(windowId),
+ eventWindowId(windowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ADDED)));
}
@@ -1019,7 +1103,7 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(windowId),
+ eventWindowId(windowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_TITLE)));
}
@@ -1173,8 +1257,6 @@
private AccessibilityWindow createMockAccessibilityWindow(IWindow windowToken, int displayId) {
final WindowInfo windowInfo = WindowInfo.obtain();
- // TODO(b/325341171): add tests with various kinds of windows such as
- // changing window types, touchable or not, trusted or not, etc.
windowInfo.type = WindowManager.LayoutParams.TYPE_APPLICATION;
windowInfo.token = windowToken.asBinder();
@@ -1235,16 +1317,16 @@
}
}
- static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
+ static class EventWindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
private int mWindowId;
- WindowIdMatcher(int windowId) {
+ EventWindowIdMatcher(int windowId) {
super();
mWindowId = windowId;
}
- static WindowIdMatcher a11yWindowId(int windowId) {
- return new WindowIdMatcher(windowId);
+ static EventWindowIdMatcher eventWindowId(int windowId) {
+ return new EventWindowIdMatcher(windowId);
}
@Override
@@ -1280,4 +1362,27 @@
description.appendText("Matching to window changes " + mWindowChanges);
}
}
+
+ static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityWindowInfo> {
+ private final int mWindowId;
+
+ WindowIdMatcher(int windowId) {
+ super();
+ mWindowId = windowId;
+ }
+
+ static WindowIdMatcher windowId(int windowId) {
+ return new WindowIdMatcher(windowId);
+ }
+
+ @Override
+ protected boolean matchesSafely(AccessibilityWindowInfo window) {
+ return window.getId() == mWindowId;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Matching to windowId " + mWindowId);
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index ea1a68a..7891661 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -1342,7 +1342,7 @@
@Override
protected int broadcastIntent(Intent intent, String resolvedType,
IIntentReceiver resultTo, int resultCode, String resultData, Bundle resultExtras,
- String[] requiredPermissions, int appOp, Bundle bOptions, boolean ordered,
+ String[] requiredPermissions, int appOp, Bundle bOptions,
boolean sticky, int callingPid, int callingUid, int realCallingUid,
int realCallingPid, int userId) {
Log.i(TAG, "broadcastIntentLocked " + intent);
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
index ef15f60..36b163e 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -173,6 +173,25 @@
}
@Test
+ public void testGetLoggableChanges() throws Exception {
+ final long disabledChangeId = 1234L;
+ final long enabledLatestChangeId = 2345L;
+ final long enabledOlderChangeId = 3456L;
+ CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+ // Disabled changes should not be logged.
+ .addDisabledChangeWithId(disabledChangeId)
+ // A change targeting the latest sdk should be logged.
+ .addEnableSinceSdkChangeWithId(3, enabledLatestChangeId)
+ // A change targeting an old sdk should not be logged.
+ .addEnableSinceSdkChangeWithId(1, enabledOlderChangeId)
+ .build();
+
+ assertThat(compatConfig.getLoggableChanges(
+ ApplicationInfoBuilder.create().withTargetSdk(3).build()))
+ .asList().containsExactly(enabledLatestChangeId);
+ }
+
+ @Test
public void testPackageOverrideEnabled() throws Exception {
CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
.addDisabledChangeWithId(1234L)
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
index 1dd64ff..5582e13 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -145,6 +145,7 @@
@SmallTest
@Test
+ @Ignore("b/277916462")
public void testCompMigrationUnAffiliated_skipped() throws Exception {
prepareAdmin1AsDo();
prepareAdminAnotherPackageAsPo(COPE_PROFILE_USER_ID);
@@ -216,6 +217,7 @@
@SmallTest
@Test
+ @Ignore("b/277916462")
public void testCompMigration_keepSuspendedAppsWhenDpcIsRPlus() throws Exception {
prepareAdmin1AsDo();
prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.R);
@@ -249,6 +251,7 @@
@SmallTest
@Test
+ @Ignore("b/277916462")
public void testCompMigration_unsuspendAppsWhenDpcNotRPlus() throws Exception {
prepareAdmin1AsDo();
prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.Q);
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index b705077..733f056 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -16,7 +16,7 @@
package com.android.server.devicestate;
-import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
import static com.android.compatibility.common.util.PollingCheck.waitFor;
@@ -48,8 +48,6 @@
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowProcessController;
-import junit.framework.Assert;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -57,6 +55,8 @@
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable;
@@ -69,21 +69,25 @@
@Presubmit
@RunWith(AndroidJUnit4.class)
public final class DeviceStateManagerServiceTest {
- private static final DeviceState DEFAULT_DEVICE_STATE =
- new DeviceState(0, "DEFAULT", 0 /* flags */);
- private static final DeviceState OTHER_DEVICE_STATE =
- new DeviceState(1, "OTHER", 0 /* flags */);
- private static final DeviceState
- DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP =
- new DeviceState(2, "DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP",
- DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP /* flags */);
- // A device state that is not reported as being supported for the default test provider.
- private static final DeviceState UNSUPPORTED_DEVICE_STATE =
- new DeviceState(255, "UNSUPPORTED", 0 /* flags */);
+ private static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(
+ new DeviceState.Configuration.Builder(0, "DEFAULT").build());
+ private static final DeviceState OTHER_DEVICE_STATE = new DeviceState(
+ new DeviceState.Configuration.Builder(1, "DEFAULT").build());
+ private static final DeviceState DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP =
+ new DeviceState(new DeviceState.Configuration.Builder(2,
+ "DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP")
+ .setSystemProperties(new HashSet<>(List.of(
+ DeviceState.PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP)))
+ .build());
- private static final int[] SUPPORTED_DEVICE_STATE_IDENTIFIERS =
- new int[]{DEFAULT_DEVICE_STATE.getIdentifier(), OTHER_DEVICE_STATE.getIdentifier(),
- DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP.getIdentifier()};
+ // A device state that is not reported as being supported for the default test provider.
+ private static final DeviceState UNSUPPORTED_DEVICE_STATE = new DeviceState(
+ new DeviceState.Configuration.Builder(255, "UNSUPPORTED")
+ .build());
+
+ private static final List<DeviceState> SUPPORTED_DEVICE_STATES = Arrays.asList(
+ DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE,
+ DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
private static final int FAKE_PROCESS_ID = 100;
@@ -201,9 +205,8 @@
@Test
public void baseStateChanged_invalidState() {
- assertThrows(IllegalArgumentException.class, () -> {
- mProvider.setState(INVALID_DEVICE_STATE);
- });
+ assertThrows(IllegalArgumentException.class,
+ () -> mProvider.setState(INVALID_DEVICE_STATE_IDENTIFIER));
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.empty());
@@ -224,7 +227,7 @@
assertEquals(mSysPropSetter.getValue(),
DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE,
+ assertThat(mService.getSupportedStates()).containsExactly(DEFAULT_DEVICE_STATE,
OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
mProvider.notifySupportedDeviceStates(new DeviceState[]{DEFAULT_DEVICE_STATE});
@@ -237,10 +240,9 @@
assertEquals(mSysPropSetter.getValue(),
DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE);
+ assertThat(mService.getSupportedStates()).containsExactly(DEFAULT_DEVICE_STATE);
- assertArrayEquals(callback.getLastNotifiedInfo().supportedStates,
- new int[]{DEFAULT_DEVICE_STATE.getIdentifier()});
+ assertEquals(callback.getLastNotifiedInfo().supportedStates, List.of(DEFAULT_DEVICE_STATE));
}
@Test
@@ -257,7 +259,7 @@
assertEquals(mSysPropSetter.getValue(),
DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE,
+ assertThat(mService.getSupportedStates()).containsExactly(DEFAULT_DEVICE_STATE,
OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
mProvider.notifySupportedDeviceStates(new DeviceState[]{DEFAULT_DEVICE_STATE,
@@ -271,7 +273,7 @@
assertEquals(mSysPropSetter.getValue(),
DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
- assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE,
+ assertThat(mService.getSupportedStates()).containsExactly(DEFAULT_DEVICE_STATE,
OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
// The callback wasn't notified about a change in supported states as the states have not
@@ -283,9 +285,9 @@
public void getDeviceStateInfo() throws RemoteException {
DeviceStateInfo info = mService.getBinderService().getDeviceStateInfo();
assertNotNull(info);
- assertArrayEquals(info.supportedStates, SUPPORTED_DEVICE_STATE_IDENTIFIERS);
- assertEquals(info.baseState, DEFAULT_DEVICE_STATE.getIdentifier());
- assertEquals(info.currentState, DEFAULT_DEVICE_STATE.getIdentifier());
+ assertEquals(info.supportedStates, SUPPORTED_DEVICE_STATES);
+ assertEquals(info.baseState, DEFAULT_DEVICE_STATE);
+ assertEquals(info.currentState, DEFAULT_DEVICE_STATE);
}
@FlakyTest(bugId = 297949293)
@@ -299,9 +301,9 @@
DeviceStateInfo info = mService.getBinderService().getDeviceStateInfo();
- assertArrayEquals(info.supportedStates, SUPPORTED_DEVICE_STATE_IDENTIFIERS);
- assertEquals(info.baseState, INVALID_DEVICE_STATE);
- assertEquals(info.currentState, INVALID_DEVICE_STATE);
+ assertEquals(info.supportedStates, SUPPORTED_DEVICE_STATES);
+ assertEquals(info.baseState.getIdentifier(), INVALID_DEVICE_STATE_IDENTIFIER);
+ assertEquals(info.currentState.getIdentifier(), INVALID_DEVICE_STATE_IDENTIFIER);
}
@Test
@@ -310,33 +312,33 @@
mService.getBinderService().registerCallback(callback);
mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
- waitAndAssert(() -> callback.getLastNotifiedInfo().baseState
+ waitAndAssert(() -> callback.getLastNotifiedInfo().baseState.getIdentifier()
== OTHER_DEVICE_STATE.getIdentifier());
- waitAndAssert(() -> callback.getLastNotifiedInfo().currentState
+ waitAndAssert(() -> callback.getLastNotifiedInfo().currentState.getIdentifier()
== OTHER_DEVICE_STATE.getIdentifier());
mProvider.setState(DEFAULT_DEVICE_STATE.getIdentifier());
- waitAndAssert(() -> callback.getLastNotifiedInfo().baseState
+ waitAndAssert(() -> callback.getLastNotifiedInfo().baseState.getIdentifier()
== DEFAULT_DEVICE_STATE.getIdentifier());
- waitAndAssert(() -> callback.getLastNotifiedInfo().currentState
+ waitAndAssert(() -> callback.getLastNotifiedInfo().currentState.getIdentifier()
== DEFAULT_DEVICE_STATE.getIdentifier());
mPolicy.blockConfigure();
mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
// The callback should not have been notified of the state change as the policy is still
// pending callback.
- waitAndAssert(() -> callback.getLastNotifiedInfo().baseState
+ waitAndAssert(() -> callback.getLastNotifiedInfo().baseState.getIdentifier()
== DEFAULT_DEVICE_STATE.getIdentifier());
- waitAndAssert(() -> callback.getLastNotifiedInfo().currentState
+ waitAndAssert(() -> callback.getLastNotifiedInfo().currentState.getIdentifier()
== DEFAULT_DEVICE_STATE.getIdentifier());
mPolicy.resumeConfigure();
// Now that the policy is finished processing the callback should be notified of the state
// change.
- waitAndAssert(() -> callback.getLastNotifiedInfo().baseState
+ waitAndAssert(() -> callback.getLastNotifiedInfo().baseState.getIdentifier()
== OTHER_DEVICE_STATE.getIdentifier());
- waitAndAssert(() -> callback.getLastNotifiedInfo().currentState
+ waitAndAssert(() -> callback.getLastNotifiedInfo().currentState.getIdentifier()
== OTHER_DEVICE_STATE.getIdentifier());
}
@@ -346,10 +348,8 @@
mService.getBinderService().registerCallback(callback);
flushHandler();
assertNotNull(callback.getLastNotifiedInfo());
- assertEquals(callback.getLastNotifiedInfo().baseState,
- DEFAULT_DEVICE_STATE.getIdentifier());
- assertEquals(callback.getLastNotifiedInfo().currentState,
- DEFAULT_DEVICE_STATE.getIdentifier());
+ assertEquals(callback.getLastNotifiedInfo().baseState, DEFAULT_DEVICE_STATE);
+ assertEquals(callback.getLastNotifiedInfo().currentState, DEFAULT_DEVICE_STATE);
}
@Test
@@ -392,10 +392,8 @@
OTHER_DEVICE_STATE.getIdentifier());
assertNotNull(callback.getLastNotifiedInfo());
- assertEquals(callback.getLastNotifiedInfo().baseState,
- DEFAULT_DEVICE_STATE.getIdentifier());
- assertEquals(callback.getLastNotifiedInfo().currentState,
- OTHER_DEVICE_STATE.getIdentifier());
+ assertEquals(callback.getLastNotifiedInfo().baseState, DEFAULT_DEVICE_STATE);
+ assertEquals(callback.getLastNotifiedInfo().currentState, OTHER_DEVICE_STATE);
mService.getBinderService().cancelStateRequest();
@@ -410,10 +408,8 @@
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
DEFAULT_DEVICE_STATE.getIdentifier());
- assertEquals(callback.getLastNotifiedInfo().baseState,
- DEFAULT_DEVICE_STATE.getIdentifier());
- assertEquals(callback.getLastNotifiedInfo().currentState,
- DEFAULT_DEVICE_STATE.getIdentifier());
+ assertEquals(callback.getLastNotifiedInfo().baseState, DEFAULT_DEVICE_STATE);
+ assertEquals(callback.getLastNotifiedInfo().currentState, DEFAULT_DEVICE_STATE);
}
@FlakyTest(bugId = 200332057)
@@ -636,7 +632,8 @@
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
OTHER_DEVICE_STATE.getIdentifier());
- mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE });
+ mProvider.notifySupportedDeviceStates(
+ new DeviceState[]{DEFAULT_DEVICE_STATE});
flushHandler();
// Request is canceled because the state is no longer supported.
@@ -672,7 +669,8 @@
assertThrows(IllegalArgumentException.class, () -> {
final IBinder token = new Binder();
- mService.getBinderService().requestState(token, INVALID_DEVICE_STATE, 0 /* flags */);
+ mService.getBinderService().requestState(token, INVALID_DEVICE_STATE_IDENTIFIER,
+ 0 /* flags */);
});
}
@@ -712,10 +710,8 @@
OTHER_DEVICE_STATE.getIdentifier());
assertNotNull(callback.getLastNotifiedInfo());
- assertEquals(callback.getLastNotifiedInfo().baseState,
- OTHER_DEVICE_STATE.getIdentifier());
- assertEquals(callback.getLastNotifiedInfo().currentState,
- OTHER_DEVICE_STATE.getIdentifier());
+ assertEquals(callback.getLastNotifiedInfo().baseState, OTHER_DEVICE_STATE);
+ assertEquals(callback.getLastNotifiedInfo().currentState, OTHER_DEVICE_STATE);
mService.getBinderService().cancelBaseStateOverride();
@@ -731,19 +727,19 @@
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
DEFAULT_DEVICE_STATE.getIdentifier());
- waitAndAssert(() -> callback.getLastNotifiedInfo().baseState
+ waitAndAssert(() -> callback.getLastNotifiedInfo().baseState.getIdentifier()
== DEFAULT_DEVICE_STATE.getIdentifier());
- assertEquals(callback.getLastNotifiedInfo().currentState,
- DEFAULT_DEVICE_STATE.getIdentifier());
+ assertEquals(callback.getLastNotifiedInfo().currentState, DEFAULT_DEVICE_STATE);
}
@Test
public void requestBaseStateOverride_cancelledByBaseStateUpdate() throws RemoteException {
- final DeviceState testDeviceState = new DeviceState(2, "TEST", 0);
+ final DeviceState testDeviceState = new DeviceState(new DeviceState.Configuration.Builder(2,
+ "TEST").build());
TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
- mProvider.notifySupportedDeviceStates(
- new DeviceState[]{DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE, testDeviceState });
+ mProvider.notifySupportedDeviceStates(new DeviceState[]{DEFAULT_DEVICE_STATE,
+ OTHER_DEVICE_STATE, testDeviceState});
flushHandler();
final IBinder token = new Binder();
@@ -767,10 +763,8 @@
OTHER_DEVICE_STATE.getIdentifier());
assertNotNull(callback.getLastNotifiedInfo());
- assertEquals(callback.getLastNotifiedInfo().baseState,
- OTHER_DEVICE_STATE.getIdentifier());
- assertEquals(callback.getLastNotifiedInfo().currentState,
- OTHER_DEVICE_STATE.getIdentifier());
+ assertEquals(callback.getLastNotifiedInfo().baseState, OTHER_DEVICE_STATE);
+ assertEquals(callback.getLastNotifiedInfo().currentState, OTHER_DEVICE_STATE);
mProvider.setState(testDeviceState.getIdentifier());
@@ -786,10 +780,9 @@
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
testDeviceState.getIdentifier());
- waitAndAssert(() -> callback.getLastNotifiedInfo().baseState
+ waitAndAssert(() -> callback.getLastNotifiedInfo().baseState.getIdentifier()
== testDeviceState.getIdentifier());
- assertEquals(callback.getLastNotifiedInfo().currentState,
- testDeviceState.getIdentifier());
+ assertEquals(callback.getLastNotifiedInfo().currentState, testDeviceState);
}
@Test
@@ -811,8 +804,8 @@
assertThrows(IllegalArgumentException.class, () -> {
final IBinder token = new Binder();
- mService.getBinderService().requestBaseStateOverride(token, INVALID_DEVICE_STATE,
- 0 /* flags */);
+ mService.getBinderService().requestBaseStateOverride(token,
+ INVALID_DEVICE_STATE_IDENTIFIER, 0 /* flags */);
});
}
@@ -826,10 +819,6 @@
});
}
- private static void assertArrayEquals(int[] expected, int[] actual) {
- Assert.assertTrue(Arrays.equals(expected, actual));
- }
-
/**
* Common code to verify the handling of FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP flag.
*
@@ -892,7 +881,8 @@
* @param isOverrideState whether a state override is active.
*/
private void assertDeviceStateConditions(
- DeviceState state, DeviceState baseState, boolean isOverrideState) {
+ DeviceState state, DeviceState baseState,
+ boolean isOverrideState) {
assertEquals(mService.getCommittedState(), Optional.of(state));
assertEquals(mService.getBaseState(), Optional.of(baseState));
assertEquals(mSysPropSetter.getValue(),
@@ -910,7 +900,7 @@
private static final class TestDeviceStatePolicy extends DeviceStatePolicy {
private final DeviceStateProvider mProvider;
- private int mLastDeviceStateRequestedToConfigure = INVALID_DEVICE_STATE;
+ private int mLastDeviceStateRequestedToConfigure = INVALID_DEVICE_STATE_IDENTIFIER;
private boolean mConfigureBlocked = false;
private Runnable mPendingConfigureCompleteRunnable;
@@ -970,10 +960,11 @@
}
private static final class TestDeviceStateProvider implements DeviceStateProvider {
- private DeviceState[] mSupportedDeviceStates = new DeviceState[]{
- DEFAULT_DEVICE_STATE,
- OTHER_DEVICE_STATE,
- DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP};
+ private DeviceState[] mSupportedDeviceStates =
+ new DeviceState[]{
+ DEFAULT_DEVICE_STATE,
+ OTHER_DEVICE_STATE,
+ DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP};
@Nullable private final DeviceState mInitialState;
private Listener mListener;
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
index cfdb586..637bf03 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
@@ -49,10 +49,10 @@
@RunWith(AndroidJUnit4.class)
public final class OverrideRequestControllerTest {
- private static final DeviceState
- TEST_DEVICE_STATE_ZERO = new DeviceState(0, "TEST_STATE", 0);
- private static final DeviceState
- TEST_DEVICE_STATE_ONE = new DeviceState(1, "TEST_STATE", 0);
+ private static final DeviceState TEST_DEVICE_STATE_ZERO = new DeviceState(
+ new DeviceState.Configuration.Builder(0, "TEST_STATE").build());
+ private static final DeviceState TEST_DEVICE_STATE_ONE = new DeviceState(
+ new DeviceState.Configuration.Builder(1, "TEST_STATE").build());
private TestStatusChangeListener mStatusListener;
private OverrideRequestController mController;
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 1249707..67b131f 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -206,6 +206,7 @@
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.MethodRule;
@@ -2150,12 +2151,14 @@
assertFalse(mService.isUidNetworkingBlocked(UID_E, false));
}
+ @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testBackgroundChainEnabled() throws Exception {
verify(mNetworkManager).setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, true);
}
+ @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testBackgroundChainOnProcStateChange() throws Exception {
@@ -2185,6 +2188,7 @@
assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
}
+ @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testBackgroundChainOnAllowlistChange() throws Exception {
@@ -2223,6 +2227,7 @@
assertFalse(mService.isUidNetworkingBlocked(UID_B, false));
}
+ @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testBackgroundChainOnTempAllowlistChange() throws Exception {
@@ -2261,6 +2266,7 @@
&& uidState.procState == procState && uidState.capability == capability;
}
+ @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testUidObserverFiltersProcStateChanges() throws Exception {
@@ -2323,6 +2329,7 @@
waitForUidEventHandlerIdle();
}
+ @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testUidObserverFiltersStaleChanges() throws Exception {
@@ -2343,6 +2350,7 @@
waitForUidEventHandlerIdle();
}
+ @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testUidObserverFiltersCapabilityChanges() throws Exception {
@@ -2422,6 +2430,7 @@
assertFalse(mService.isUidNetworkingBlocked(UID_A, false));
}
+ @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testObsoleteHandleUidChanged() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 16909ab..fad10f7 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -60,7 +60,10 @@
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Unit tests for {@link DeviceStateProviderImpl}.
@@ -68,10 +71,15 @@
* Run with <code>atest DeviceStateProviderImplTest</code>.
*/
public final class DeviceStateProviderImplTest {
- private final ArgumentCaptor<DeviceState[]> mDeviceStateArrayCaptor = ArgumentCaptor.forClass(
- DeviceState[].class);
+ private final ArgumentCaptor<DeviceState[]> mDeviceStateArrayCaptor =
+ ArgumentCaptor.forClass(DeviceState[].class);
private final ArgumentCaptor<Integer> mIntegerCaptor = ArgumentCaptor.forClass(Integer.class);
private static final int MAX_HINGE_ANGLE_EXCLUSIVE = 360;
+ private static final Set<Integer> EMPTY_PROPERTY_SET = new HashSet<>();
+ private static final Set<Integer> THERMAL_TEST_PROPERTY_SET = new HashSet<>(
+ Arrays.asList(DeviceState.PROPERTY_EMULATED_ONLY,
+ DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL,
+ DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE));
private Context mContext;
private SensorManager mSensorManager;
@@ -160,8 +168,8 @@
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
final DeviceState[] expectedStates = new DeviceState[]{
- new DeviceState(1, "", 0 /* flags */),
- new DeviceState(2, "", 0 /* flags */) };
+ createDeviceState(1, "", EMPTY_PROPERTY_SET),
+ createDeviceState(2, "", EMPTY_PROPERTY_SET)};
assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue());
verify(listener).onStateChanged(mIntegerCaptor.capture());
@@ -169,13 +177,13 @@
}
@Test
- public void create_stateWithCancelOverrideRequestFlag() {
+ public void create_stateWithCancelOverrideRequestProperty() {
String configString = "<device-state-config>\n"
+ " <device-state>\n"
+ " <identifier>1</identifier>\n"
- + " <flags>\n"
- + " <flag>FLAG_CANCEL_OVERRIDE_REQUESTS</flag>\n"
- + " </flags>\n"
+ + " <properties>\n"
+ + " <property>PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS</property>\n"
+ + " </properties>\n"
+ " <conditions/>\n"
+ " </device-state>\n"
+ " <device-state>\n"
@@ -192,20 +200,22 @@
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
+
final DeviceState[] expectedStates = new DeviceState[]{
- new DeviceState(1, "", DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS),
- new DeviceState(2, "", 0 /* flags */) };
+ createDeviceState(1, "", new HashSet<>(
+ List.of(DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS))),
+ createDeviceState(2, "", EMPTY_PROPERTY_SET)};
assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue());
}
@Test
- public void create_stateWithInvalidFlag() {
+ public void create_stateWithInvalidProperty() {
String configString = "<device-state-config>\n"
+ " <device-state>\n"
+ " <identifier>1</identifier>\n"
- + " <flags>\n"
- + " <flag>INVALID_FLAG</flag>\n"
- + " </flags>\n"
+ + " <properties>\n"
+ + " <property>INVALID_PROPERTY</property>\n"
+ + " </properties>\n"
+ " <conditions/>\n"
+ " </device-state>\n"
+ " <device-state>\n"
@@ -223,8 +233,8 @@
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
final DeviceState[] expectedStates = new DeviceState[]{
- new DeviceState(1, "", 0 /* flags */),
- new DeviceState(2, "", 0 /* flags */) };
+ createDeviceState(1, "", EMPTY_PROPERTY_SET),
+ createDeviceState(2, "", EMPTY_PROPERTY_SET)};
assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue());
}
@@ -259,8 +269,8 @@
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
final DeviceState[] expectedStates = new DeviceState[]{
- new DeviceState(1, "", 0 /* flags */),
- new DeviceState(2, "CLOSED", 0 /* flags */) };
+ createDeviceState(1, "", EMPTY_PROPERTY_SET),
+ createDeviceState(2, "CLOSED", EMPTY_PROPERTY_SET)};
assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue());
// onStateChanged() should not be called because the provider has not yet been notified of
@@ -327,11 +337,13 @@
+ " <device-state>\n"
+ " <identifier>4</identifier>\n"
+ " <name>THERMAL_TEST</name>\n"
- + " <flags>\n"
- + " <flag>FLAG_EMULATED_ONLY</flag>\n"
- + " <flag>FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL</flag>\n"
- + " <flag>FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE</flag>\n"
- + " </flags>\n"
+ + " <properties>\n"
+ + " <property>PROPERTY_EMULATED_ONLY</property>\n"
+ + " <property>PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL"
+ + "</property>\n"
+ + " <property>PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE"
+ + "</property>\n"
+ + " </properties>\n"
+ " </device-state>\n"
+ "</device-state-config>\n";
DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
@@ -352,13 +364,11 @@
eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
assertArrayEquals(
new DeviceState[]{
- new DeviceState(1, "CLOSED", 0 /* flags */),
- new DeviceState(2, "HALF_OPENED", 0 /* flags */),
- new DeviceState(3, "OPENED", 0 /* flags */),
- new DeviceState(4, "THERMAL_TEST",
- DeviceState.FLAG_EMULATED_ONLY
- | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
- | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) },
+ createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+ createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(4, "THERMAL_TEST",
+ THERMAL_TEST_PROPERTY_SET)},
mDeviceStateArrayCaptor.getValue());
// onStateChanged() should not be called because the provider has not yet been notified of
// the initial sensor state.
@@ -405,7 +415,7 @@
}
@Test
- public void test_flagDisableWhenThermalStatusCritical() throws Exception {
+ public void test_propertyDisableWhenThermalStatusCritical() throws Exception {
Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of(sensor));
DeviceStateProviderImpl provider = create_sensorBasedProvider(sensor);
@@ -418,13 +428,11 @@
eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
assertArrayEquals(
new DeviceState[]{
- new DeviceState(1, "CLOSED", 0 /* flags */),
- new DeviceState(2, "HALF_OPENED", 0 /* flags */),
- new DeviceState(3, "OPENED", 0 /* flags */),
- new DeviceState(4, "THERMAL_TEST",
- DeviceState.FLAG_EMULATED_ONLY
- | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
- | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) },
+ createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+ createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(4, "THERMAL_TEST",
+ THERMAL_TEST_PROPERTY_SET)},
mDeviceStateArrayCaptor.getValue());
Mockito.clearInvocations(listener);
@@ -439,9 +447,9 @@
eq(SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_CRITICAL));
assertArrayEquals(
new DeviceState[]{
- new DeviceState(1, "CLOSED", 0 /* flags */),
- new DeviceState(2, "HALF_OPENED", 0 /* flags */),
- new DeviceState(3, "OPENED", 0 /* flags */) },
+ createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+ createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET)},
mDeviceStateArrayCaptor.getValue());
Mockito.clearInvocations(listener);
@@ -451,18 +459,16 @@
eq(SUPPORTED_DEVICE_STATES_CHANGED_THERMAL_NORMAL));
assertArrayEquals(
new DeviceState[]{
- new DeviceState(1, "CLOSED", 0 /* flags */),
- new DeviceState(2, "HALF_OPENED", 0 /* flags */),
- new DeviceState(3, "OPENED", 0 /* flags */),
- new DeviceState(4, "THERMAL_TEST",
- DeviceState.FLAG_EMULATED_ONLY
- | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
- | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) },
+ createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+ createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(4, "THERMAL_TEST",
+ THERMAL_TEST_PROPERTY_SET)},
mDeviceStateArrayCaptor.getValue());
}
@Test
- public void test_flagDisableWhenPowerSaveEnabled() throws Exception {
+ public void test_propertyDisableWhenPowerSaveEnabled() throws Exception {
Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of(sensor));
DeviceStateProviderImpl provider = create_sensorBasedProvider(sensor);
@@ -475,13 +481,11 @@
eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
assertArrayEquals(
new DeviceState[]{
- new DeviceState(1, "CLOSED", 0 /* flags */),
- new DeviceState(2, "HALF_OPENED", 0 /* flags */),
- new DeviceState(3, "OPENED", 0 /* flags */),
- new DeviceState(4, "THERMAL_TEST",
- DeviceState.FLAG_EMULATED_ONLY
- | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
- | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) },
+ createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+ createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(4, "THERMAL_TEST",
+ THERMAL_TEST_PROPERTY_SET)},
mDeviceStateArrayCaptor.getValue());
Mockito.clearInvocations(listener);
@@ -496,9 +500,9 @@
eq(SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_ENABLED));
assertArrayEquals(
new DeviceState[]{
- new DeviceState(1, "CLOSED", 0 /* flags */),
- new DeviceState(2, "HALF_OPENED", 0 /* flags */),
- new DeviceState(3, "OPENED", 0 /* flags */) },
+ createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+ createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET)},
mDeviceStateArrayCaptor.getValue());
Mockito.clearInvocations(listener);
@@ -508,13 +512,11 @@
eq(SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED));
assertArrayEquals(
new DeviceState[]{
- new DeviceState(1, "CLOSED", 0 /* flags */),
- new DeviceState(2, "HALF_OPENED", 0 /* flags */),
- new DeviceState(3, "OPENED", 0 /* flags */),
- new DeviceState(4, "THERMAL_TEST",
- DeviceState.FLAG_EMULATED_ONLY
- | DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL
- | DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE) },
+ createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+ createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(3, "OPENED", EMPTY_PROPERTY_SET),
+ createDeviceState(4, "THERMAL_TEST",
+ THERMAL_TEST_PROPERTY_SET)},
mDeviceStateArrayCaptor.getValue());
}
@@ -598,13 +600,22 @@
eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
assertArrayEquals(
new DeviceState[]{
- new DeviceState(1, "CLOSED", 0 /* flags */),
- new DeviceState(2, "HALF_OPENED", 0 /* flags */)
+ createDeviceState(1, "CLOSED", EMPTY_PROPERTY_SET),
+ createDeviceState(2, "HALF_OPENED", EMPTY_PROPERTY_SET)
}, mDeviceStateArrayCaptor.getValue());
// onStateChanged() should not be called because the provider could not find the sensor.
verify(listener, never()).onStateChanged(mIntegerCaptor.capture());
}
+ private DeviceState createDeviceState(int identifier, @NonNull String name,
+ @NonNull Set<@DeviceState.DeviceStateProperties Integer> systemProperties) {
+ DeviceState.Configuration configuration = new DeviceState.Configuration.Builder(identifier,
+ name)
+ .setSystemProperties(systemProperties)
+ .build();
+ return new DeviceState(configuration);
+ }
+
private static Sensor newSensor(String name, String type) throws Exception {
Constructor<Sensor> constructor = Sensor.class.getDeclaredConstructor();
constructor.setAccessible(true);
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 03f2749..f29d215 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -6223,6 +6223,52 @@
}
@Test
+ public void testSensitiveAdjustmentsLogged() throws Exception {
+ NotificationManagerService.WorkerHandler handler = mock(
+ NotificationManagerService.WorkerHandler.class);
+ mService.setHandler(handler);
+ when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
+ when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
+
+ // Set up notifications that will be adjusted
+ final NotificationRecord r1 = spy(generateNotificationRecord(
+ mTestNotificationChannel, 1, null, true));
+ when(r1.getLifespanMs(anyLong())).thenReturn(1);
+
+ r1.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
+ mService.addEnqueuedNotification(r1);
+
+ // Test an adjustment for an enqueued notification
+ Bundle signals = new Bundle();
+ signals.putBoolean(Adjustment.KEY_SENSITIVE_CONTENT, true);
+ Adjustment adjustment1 = new Adjustment(
+ r1.getSbn().getPackageName(), r1.getKey(), signals, "",
+ r1.getUser().getIdentifier());
+ mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment1);
+ assertTrue(mService.checkLastSensitiveLog(false, true, 1));
+
+ // Set up notifications that will be adjusted
+ final NotificationRecord r2 = spy(generateNotificationRecord(
+ mTestNotificationChannel, 1, null, true));
+ when(r2.getLifespanMs(anyLong())).thenReturn(2);
+
+ r2.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
+ mService.addNotification(r2);
+ Adjustment adjustment2 = new Adjustment(
+ r2.getSbn().getPackageName(), r2.getKey(), signals, "",
+ r2.getUser().getIdentifier());
+ mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment2);
+ assertTrue(mService.checkLastSensitiveLog(true, true, 2));
+
+ signals.putBoolean(Adjustment.KEY_SENSITIVE_CONTENT, false);
+ Adjustment adjustment3 = new Adjustment(
+ r2.getSbn().getPackageName(), r2.getKey(), signals, "",
+ r2.getUser().getIdentifier());
+ mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment3);
+ assertTrue(mService.checkLastSensitiveLog(true, false, 2));
+ }
+
+ @Test
public void testAdjustmentToImportanceNone_cancelsNotification() throws Exception {
NotificationManagerService.WorkerHandler handler = mock(
NotificationManagerService.WorkerHandler.class);
@@ -12008,7 +12054,7 @@
// style + self managed call - bypasses block
when(mTelecomManager.isInSelfManagedCall(
- r.getSbn().getPackageName(), true)).thenReturn(true);
+ r.getSbn().getPackageName(), UserHandle.ALL)).thenReturn(true);
assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(),
r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue();
@@ -12091,7 +12137,7 @@
// style + self managed call - bypasses block
mService.clearNotifications();
reset(mUsageStats);
- when(mTelecomManager.isInSelfManagedCall(r.getSbn().getPackageName(), true))
+ when(mTelecomManager.isInSelfManagedCall(r.getSbn().getPackageName(), UserHandle.ALL))
.thenReturn(true);
mService.addEnqueuedNotification(r);
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 6976ec3..07d25df 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
@@ -45,6 +45,13 @@
ComponentPermissionChecker permissionChecker;
+ private static class SensitiveLog {
+ public boolean hasPosted;
+ public boolean hasSensitiveContent;
+ public long lifetime;
+ }
+ public SensitiveLog lastSensitiveLog = null;
+
TestableNotificationManagerService(Context context, NotificationRecordLogger logger,
InstanceIdSequence notificationInstanceIdSequence) {
super(context, logger, notificationInstanceIdSequence);
@@ -167,6 +174,15 @@
return permissionChecker.check(permission, uid, owningUid, exported);
}
+ @Override
+ protected void logSensitiveAdjustmentReceived(boolean hasPosted, boolean hasSensitiveContent,
+ int lifetimeMs) {
+ lastSensitiveLog = new SensitiveLog();
+ lastSensitiveLog.hasPosted = hasPosted;
+ lastSensitiveLog.hasSensitiveContent = hasSensitiveContent;
+ lastSensitiveLog.lifetime = lifetimeMs;
+ }
+
public class StrongAuthTrackerFake extends NotificationManagerService.StrongAuthTracker {
private int mGetStrongAuthForUserReturnValue = 0;
StrongAuthTrackerFake(Context context) {
@@ -183,6 +199,15 @@
}
}
+ public boolean checkLastSensitiveLog(boolean hasPosted, boolean hasSensitive, int lifetime) {
+ if (lastSensitiveLog == null) {
+ return false;
+ }
+ return hasPosted == lastSensitiveLog.hasPosted
+ && hasSensitive == lastSensitiveLog.hasSensitiveContent
+ && lifetime == lastSensitiveLog.lifetime;
+ }
+
public interface ComponentPermissionChecker {
int check(String permission, int uid, int owningUid, boolean exported);
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 6e478d8..0b76154 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -87,6 +87,8 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;
@@ -110,6 +112,7 @@
@Mock private VibratorController.OnVibrationCompleteListener mControllerCallbacks;
@Mock private IBinder mVibrationToken;
@Mock private VibrationConfig mVibrationConfigMock;
+ @Mock private VibratorFrameworkStatsLogger mStatsLoggerMock;
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
private VibrationSettings mVibrationSettings;
@@ -255,6 +258,7 @@
USAGE_RINGTONE);
waitForCompletion();
+ verify(mStatsLoggerMock, never()).logVibrationParamRequestTimeout(UID);
assertEquals(Arrays.asList(expectedOneShot(15)),
mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
List<Float> amplitudes = mVibratorProviders.get(VIBRATOR_ID).getAmplitudes();
@@ -274,6 +278,7 @@
long vibrationId = startThreadAndDispatcher(effect, neverCompletingFuture, USAGE_RINGTONE);
waitForCompletion();
+ verify(mStatsLoggerMock).logVibrationParamRequestTimeout(UID);
assertEquals(Arrays.asList(expectedOneShot(15)),
mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
assertEquals(expectedAmplitudes(1, 1, 1),
@@ -1243,6 +1248,112 @@
assertEquals(expectedAmplitudes(6), mVibratorProviders.get(3).getAmplitudes());
}
+ @Test
+ public void vibrate_withRampDown_vibrationFinishedAfterDurationAndBeforeRampDown()
+ throws Exception {
+ int expectedDuration = 100;
+ int rampDownDuration = 200;
+
+ when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(rampDownDuration);
+ mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+ HalVibration vibration = createVibration(
+ CombinedVibration.createParallel(
+ VibrationEffect.createOneShot(
+ expectedDuration, VibrationEffect.DEFAULT_AMPLITUDE)));
+ CountDownLatch vibrationCompleteLatch = new CountDownLatch(1);
+ doAnswer(unused -> {
+ vibrationCompleteLatch.countDown();
+ return null;
+ }).when(mManagerHooks).onVibrationCompleted(eq(vibration.id), any());
+
+ startThreadAndDispatcher(vibration);
+ long startTime = SystemClock.elapsedRealtime();
+
+ assertTrue(vibrationCompleteLatch.await(expectedDuration + TEST_TIMEOUT_MILLIS,
+ TimeUnit.MILLISECONDS));
+ long vibrationEndTime = SystemClock.elapsedRealtime();
+
+ waitForCompletion(rampDownDuration + TEST_TIMEOUT_MILLIS);
+ long completionTime = SystemClock.elapsedRealtime();
+
+ verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibration.id);
+ // Vibration ends after duration, thread completed after ramp down
+ assertThat(vibrationEndTime - startTime).isAtLeast(expectedDuration);
+ assertThat(vibrationEndTime - startTime).isLessThan(expectedDuration + rampDownDuration);
+ assertThat(completionTime - startTime).isAtLeast(expectedDuration + rampDownDuration);
+ }
+
+ @Test
+ public void vibrate_withVibratorCallbackDelayShorterThanTimeout_vibrationFinishedAfterDelay()
+ throws Exception {
+ long expectedDuration = 10;
+ long callbackDelay = VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT / 2;
+
+ mVibratorProviders.get(VIBRATOR_ID).setCompletionCallbackDelay(callbackDelay);
+ mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+ HalVibration vibration = createVibration(
+ CombinedVibration.createParallel(
+ VibrationEffect.createOneShot(
+ expectedDuration, VibrationEffect.DEFAULT_AMPLITUDE)));
+ CountDownLatch vibrationCompleteLatch = new CountDownLatch(1);
+ doAnswer(unused -> {
+ vibrationCompleteLatch.countDown();
+ return null;
+ }).when(mManagerHooks).onVibrationCompleted(eq(vibration.id), any());
+
+ startThreadAndDispatcher(vibration);
+ long startTime = SystemClock.elapsedRealtime();
+
+ assertTrue(vibrationCompleteLatch.await(callbackDelay + TEST_TIMEOUT_MILLIS,
+ TimeUnit.MILLISECONDS));
+ long vibrationEndTime = SystemClock.elapsedRealtime();
+
+ waitForCompletion(TEST_TIMEOUT_MILLIS);
+
+ verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibration.id);
+ assertThat(vibrationEndTime - startTime).isAtLeast(expectedDuration + callbackDelay);
+ }
+
+ @LargeTest
+ @Test
+ public void vibrate_withVibratorCallbackDelayLongerThanTimeout_vibrationFinishedAfterTimeout()
+ throws Exception {
+ long expectedDuration = 10;
+ long callbackTimeout = VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT;
+ long callbackDelay = callbackTimeout * 2;
+
+ mVibratorProviders.get(VIBRATOR_ID).setCompletionCallbackDelay(callbackDelay);
+ mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+ HalVibration vibration = createVibration(
+ CombinedVibration.createParallel(
+ VibrationEffect.createOneShot(
+ expectedDuration, VibrationEffect.DEFAULT_AMPLITUDE)));
+ CountDownLatch vibrationCompleteLatch = new CountDownLatch(1);
+ doAnswer(unused -> {
+ vibrationCompleteLatch.countDown();
+ return null;
+ }).when(mManagerHooks).onVibrationCompleted(eq(vibration.id), any());
+
+ startThreadAndDispatcher(vibration);
+ long startTime = SystemClock.elapsedRealtime();
+
+ assertTrue(vibrationCompleteLatch.await(callbackTimeout + TEST_TIMEOUT_MILLIS,
+ TimeUnit.MILLISECONDS));
+ long vibrationEndTime = SystemClock.elapsedRealtime();
+
+ waitForCompletion(callbackDelay + TEST_TIMEOUT_MILLIS);
+ long completionTime = SystemClock.elapsedRealtime();
+
+ verify(mControllerCallbacks, never()).onComplete(VIBRATOR_ID, vibration.id);
+ // Vibration ends and thread completes after timeout, before the HAL callback
+ assertThat(vibrationEndTime - startTime).isAtLeast(expectedDuration + callbackTimeout);
+ assertThat(vibrationEndTime - startTime).isLessThan(expectedDuration + callbackDelay);
+ assertThat(completionTime - startTime).isLessThan(expectedDuration + callbackDelay);
+ }
+
@LargeTest
@Test
public void vibrate_withWaveform_totalVibrationTimeRespected() {
@@ -1679,7 +1790,7 @@
mControllers = createVibratorControllers();
DeviceAdapter deviceAdapter = new DeviceAdapter(mVibrationSettings, mControllers);
mVibrationConductor = new VibrationStepConductor(vib, mVibrationSettings, deviceAdapter,
- mVibrationScaler, requestVibrationParamsFuture, mManagerHooks);
+ mVibrationScaler, mStatsLoggerMock, requestVibrationParamsFuture, mManagerHooks);
assertTrue(mThread.runVibrationOnVibrationThread(mVibrationConductor));
return mVibrationConductor.getVibration().id;
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
index 3799abc..3f5217c 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
@@ -27,6 +27,10 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -38,6 +42,7 @@
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Process;
import android.os.test.TestLooper;
import android.util.SparseArray;
@@ -59,13 +64,14 @@
public class VibratorControlServiceTest {
+ private static final int UID = Process.ROOT_UID;
+
@Rule
public MockitoRule rule = MockitoJUnit.rule();
- @Mock
- private VibrationScaler mMockVibrationScaler;
- @Mock
- private PackageManagerInternal mPackageManagerInternalMock;
+ @Mock private VibrationScaler mMockVibrationScaler;
+ @Mock private PackageManagerInternal mPackageManagerInternalMock;
+ @Mock private VibratorFrameworkStatsLogger mStatsLoggerMock;
private TestLooper mTestLooper;
private FakeVibratorController mFakeVibratorController;
@@ -88,7 +94,7 @@
mFakeVibratorController = new FakeVibratorController(mTestLooper.getLooper());
mVibratorControlService = new VibratorControlService(
InstrumentationRegistry.getContext(), new VibratorControllerHolder(),
- mMockVibrationScaler, mVibrationSettings, mLock);
+ mMockVibrationScaler, mVibrationSettings, mStatsLoggerMock, mLock);
}
@Test
@@ -123,7 +129,7 @@
mVibratorControlService.registerVibratorController(mFakeVibratorController);
int timeoutInMillis = 10;
CompletableFuture<Void> future =
- mVibratorControlService.triggerVibrationParamsRequest(USAGE_RINGTONE,
+ mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
timeoutInMillis);
IBinder token = mVibratorControlService.getRequestVibrationParamsToken();
@@ -134,6 +140,11 @@
mVibratorControlService.onRequestVibrationParamsComplete(token,
VibrationParamGenerator.generateVibrationParams(vibrationScales));
+ verify(mStatsLoggerMock).logVibrationParamRequestLatency(eq(UID), anyLong());
+ verify(mStatsLoggerMock).logVibrationParamScale(0.7f);
+ verify(mStatsLoggerMock).logVibrationParamScale(0.4f);
+ verifyNoMoreInteractions(mStatsLoggerMock);
+
verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_ALARM, 0.7f);
verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_NOTIFICATION, 0.4f);
// Setting ScaleParam.TYPE_NOTIFICATION will update vibration scaling for both
@@ -150,7 +161,7 @@
mVibratorControlService.registerVibratorController(mFakeVibratorController);
int timeoutInMillis = 10;
CompletableFuture<Void> unusedFuture =
- mVibratorControlService.triggerVibrationParamsRequest(USAGE_RINGTONE,
+ mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
timeoutInMillis);
SparseArray<Float> vibrationScales = new SparseArray<>();
@@ -160,6 +171,9 @@
mVibratorControlService.onRequestVibrationParamsComplete(new Binder(),
VibrationParamGenerator.generateVibrationParams(vibrationScales));
+ verify(mStatsLoggerMock).logVibrationParamResponseIgnored();
+ verifyNoMoreInteractions(mStatsLoggerMock);
+
verifyZeroInteractions(mMockVibrationScaler);
}
@@ -174,6 +188,10 @@
VibrationParamGenerator.generateVibrationParams(vibrationScales),
mFakeVibratorController);
+ verify(mStatsLoggerMock).logVibrationParamScale(0.7f);
+ verify(mStatsLoggerMock).logVibrationParamScale(0.4f);
+ verifyNoMoreInteractions(mStatsLoggerMock);
+
verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_ALARM, 0.7f);
verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_NOTIFICATION, 0.4f);
// Setting ScaleParam.TYPE_NOTIFICATION will update vibration scaling for both
@@ -192,6 +210,7 @@
VibrationParamGenerator.generateVibrationParams(vibrationScales),
mFakeVibratorController);
+ verify(mStatsLoggerMock, never()).logVibrationParamScale(anyFloat());
verifyZeroInteractions(mMockVibrationScaler);
}
@@ -202,6 +221,8 @@
mVibratorControlService.clearVibrationParams(types, mFakeVibratorController);
+ verify(mStatsLoggerMock).logVibrationParamScale(-1f);
+
verify(mMockVibrationScaler).removeAdaptiveHapticsScale(USAGE_ALARM);
verify(mMockVibrationScaler).removeAdaptiveHapticsScale(USAGE_NOTIFICATION);
// Clearing ScaleParam.TYPE_NOTIFICATION will clear vibration scaling for both
@@ -214,6 +235,7 @@
mVibratorControlService.clearVibrationParams(ScaleParam.TYPE_ALARM,
mFakeVibratorController);
+ verify(mStatsLoggerMock, never()).logVibrationParamScale(anyFloat());
verifyZeroInteractions(mMockVibrationScaler);
}
@@ -222,7 +244,7 @@
int timeoutInMillis = 10;
mVibratorControlService.registerVibratorController(mFakeVibratorController);
CompletableFuture<Void> future =
- mVibratorControlService.triggerVibrationParamsRequest(USAGE_RINGTONE,
+ mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
timeoutInMillis);
try {
future.orTimeout(timeoutInMillis, TimeUnit.MILLISECONDS).get();
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 1ea90f5..8cbcc22 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -70,7 +70,6 @@
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
import android.os.Process;
-import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.VibrationAttributes;
@@ -1551,6 +1550,48 @@
}
@Test
+ @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ public void vibrate_withAdaptiveHaptics_appliesCorrectAdaptiveScales() throws Exception {
+ // Keep user settings the same as device default so only adaptive scale is applied.
+ setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY,
+ mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_ALARM));
+ setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+ mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_NOTIFICATION));
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
+ mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_TOUCH));
+
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK);
+ VibratorManagerService service = createSystemReadyService();
+
+ SparseArray<Float> vibrationScales = new SparseArray<>();
+ vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
+ vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
+
+ mVibratorControlService.setVibrationParams(
+ VibrationParamGenerator.generateVibrationParams(vibrationScales),
+ mFakeVibratorController);
+
+ VibrationEffect effect = VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .compose();
+ vibrateAndWaitUntilFinished(service, effect, ALARM_ATTRS);
+ vibrateAndWaitUntilFinished(service, effect, NOTIFICATION_ATTRS);
+ vibrateAndWaitUntilFinished(service, effect, HAPTIC_FEEDBACK_ATTRS);
+
+ List<VibrationEffectSegment> segments = fakeVibrator.getAllEffectSegments();
+ assertEquals(3, segments.size());
+ assertEquals(0.7f, ((PrimitiveSegment) segments.get(0)).getScale(), 1e-5);
+ assertEquals(0.4f, ((PrimitiveSegment) segments.get(1)).getScale(), 1e-5);
+ assertEquals(1f, ((PrimitiveSegment) segments.get(2)).getScale(), 1e-5);
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 0.7f);
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 0.4f);
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 1f);
+ }
+
+ @Test
public void vibrate_withPowerModeChange_cancelVibrationIfNotAllowed() throws Exception {
mockVibrators(1, 2);
VibratorManagerService service = createSystemReadyService();
@@ -1998,8 +2039,7 @@
@Test
@RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
- public void onExternalVibration_withAdaptiveHaptics_returnsCorrectAdaptiveScales()
- throws RemoteException {
+ public void onExternalVibration_withAdaptiveHaptics_returnsCorrectAdaptiveScales() {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL,
IVibrator.CAP_AMPLITUDE_CONTROL);
@@ -2020,6 +2060,7 @@
mExternalVibratorService.onExternalVibrationStop(externalVibration);
assertEquals(scale.adaptiveHapticsScale, 0.7f, 0);
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 0.7f);
externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
AUDIO_NOTIFICATION_ATTRS,
@@ -2028,6 +2069,7 @@
mExternalVibratorService.onExternalVibrationStop(externalVibration);
assertEquals(scale.adaptiveHapticsScale, 0.4f, 0);
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 0.4f);
AudioAttributes ringtoneAudioAttrs = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
@@ -2036,14 +2078,15 @@
ringtoneAudioAttrs,
mock(IExternalVibrationController.class));
scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ mExternalVibratorService.onExternalVibrationStop(externalVibration);
assertEquals(scale.adaptiveHapticsScale, 1f, 0);
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 1f);
}
@Test
@RequiresFlagsDisabled(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
- public void onExternalVibration_withAdaptiveHapticsFlagDisabled_alwaysReturnScaleNone()
- throws RemoteException {
+ public void onExternalVibration_withAdaptiveHapticsFlagDisabled_alwaysReturnScaleNone() {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL,
IVibrator.CAP_AMPLITUDE_CONTROL);
@@ -2065,15 +2108,15 @@
assertEquals(scale.adaptiveHapticsScale, 1f, 0);
- mVibratorControlService.setVibrationParams(
- VibrationParamGenerator.generateVibrationParams(vibrationScales),
- mFakeVibratorController);
externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
AUDIO_NOTIFICATION_ATTRS,
mock(IExternalVibrationController.class));
scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ mExternalVibratorService.onExternalVibrationStop(externalVibration);
assertEquals(scale.adaptiveHapticsScale, 1f, 0);
+ verify(mVibratorFrameworkStatsLoggerMock, times(2))
+ .logVibrationAdaptiveHapticScale(UID, 1f);
}
@Test
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
index 12815fa..2ddb47b 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -53,6 +53,7 @@
private boolean mIsAvailable = true;
private boolean mIsInfoLoadSuccessful = true;
+ private long mCompletionCallbackDelay;
private long mOnLatency;
private long mOffLatency;
private int mOffCount;
@@ -206,7 +207,7 @@
private void scheduleListener(long vibrationDuration, long vibrationId) {
mHandler.postDelayed(() -> listener.onComplete(vibratorId, vibrationId),
- vibrationDuration);
+ vibrationDuration + mCompletionCallbackDelay);
}
}
@@ -241,6 +242,13 @@
}
/**
+ * Sets the delay this controller should fake for triggering the vibration completed callback.
+ */
+ public void setCompletionCallbackDelay(long millis) {
+ mCompletionCallbackDelay = millis;
+ }
+
+ /**
* Sets the latency this controller should fake for turning the vibrator hardware on or setting
* the vibration amplitude.
*/
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 45a2ba4..daa5a5a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3683,7 +3683,9 @@
assertEquals(WINDOWING_MODE_FULLSCREEN, activity.getWindowingMode());
registerTestTransitionPlayer();
- task.mTransitionController.requestTransitionIfNeeded(TRANSIT_PIP, task);
+ Transition tr = task.mTransitionController.requestStartTransition(
+ task.mTransitionController.createTransition(TRANSIT_PIP), task, null, null);
+ tr.collect(task);
task.setWindowingMode(WINDOWING_MODE_PINNED);
// Collect activity in the transition if the Task windowing mode is going to change.
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
index 99d354a..b11f9b2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
@@ -70,6 +70,7 @@
@Before
public void before() {
+ doReturn(true).when(mDisplayContent).getLastHasContent();
mockTransitionsController(/* enabled= */ true);
mockRemoteDisplayChangeController();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index 38a66a9..eeec54f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -411,6 +411,10 @@
app2.mAboveInsetsState.addSource(statusBarSource);
assertTrue(app2.getInsetsState().peekSource(statusBarId).isVisible());
+ // Let app2 be the focused window. Otherwise, the control target could be overwritten by
+ // DisplayPolicy#updateSystemBarAttributes unexpectedly.
+ mDisplayContent.getDisplayPolicy().focusChangedLw(null, app2);
+
app2.setRequestedVisibleTypes(0, navigationBars() | statusBars());
mDisplayContent.getInsetsPolicy().updateBarControlTarget(app2);
waitUntilWindowAnimatorIdle();
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index 32b3558..da437c4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -33,6 +33,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.ActivityRecord.State.PAUSED;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -164,13 +165,12 @@
ActivityRecord recentsActivity = recentsStack.getTopNonFinishingActivity();
// The activity is started in background so it should be invisible and will be stopped.
assertThat(recentsActivity).isNotNull();
- assertThat(mSupervisor.mStoppingActivities).contains(recentsActivity);
+ assertThat(recentsActivity.getState()).isEqualTo(STOPPING);
assertFalse(recentsActivity.isVisibleRequested());
// Assume it is stopped to test next use case.
recentsActivity.activityStopped(null /* newIcicle */, null /* newPersistentState */,
null /* description */);
- mSupervisor.mStoppingActivities.remove(recentsActivity);
spyOn(recentsActivity);
// Start when the recents activity exists. It should ensure the configuration.
@@ -178,7 +178,6 @@
null /* recentsAnimationRunner */);
verify(recentsActivity).ensureActivityConfiguration(eq(true) /* ignoreVisibility */);
- assertThat(mSupervisor.mStoppingActivities).contains(recentsActivity);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 961fdfb..10cfb5f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -315,6 +315,23 @@
}
@Test
+ public void testUserLeaving() {
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final Task task = activity.getTask();
+ mSupervisor.mUserLeaving = true;
+ activity.setState(ActivityRecord.State.RESUMED, "test");
+ task.sleepIfPossible(false /* shuttingDown */);
+ verify(task).startPausing(eq(true) /* userLeaving */, anyBoolean(), any(), any());
+
+ clearInvocations(task);
+ activity.setState(ActivityRecord.State.RESUMED, "test");
+ task.setPausingActivity(null);
+ doReturn(false).when(task).canBeResumed(any());
+ task.pauseActivityIfNeeded(null /* resuming */, "test");
+ verify(task).startPausing(eq(true) /* userLeaving */, anyBoolean(), any(), any());
+ }
+
+ @Test
public void testSwitchUser() {
final Task rootTask = createTask(mDisplayContent);
final Task childTask = createTaskInRootTask(rootTask, 0 /* userId */);
@@ -1823,6 +1840,66 @@
}
@Test
+ public void testAssignChildLayers_boostedDecorSurfacePlacement() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
+ final ActivityRecord unembeddedActivity = task.getTopMostActivity();
+
+ final TaskFragment fragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final TaskFragment fragment2 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final SurfaceControl.Transaction t = task.getSyncTransaction();
+ final SurfaceControl.Transaction clientTransaction = mock(SurfaceControl.Transaction.class);
+
+ doNothing().when(task).sendTaskFragmentParentInfoChangedIfNeeded();
+ spyOn(unembeddedActivity);
+ spyOn(fragment1);
+ spyOn(fragment2);
+
+ doReturn(true).when(unembeddedActivity).isUid(task.effectiveUid);
+ doReturn(true).when(fragment1).isAllowedToBeEmbeddedInTrustedMode();
+ doReturn(false).when(fragment2).isAllowedToBeEmbeddedInTrustedMode();
+ doReturn(true).when(fragment1).isVisible();
+
+ task.moveOrCreateDecorSurfaceFor(fragment1);
+
+ clearInvocations(t);
+ clearInvocations(unembeddedActivity);
+ clearInvocations(fragment1);
+ clearInvocations(fragment2);
+
+ // The decor surface should be placed above all the windows when boosted and the cover
+ // surface should show.
+ task.setDecorSurfaceBoosted(fragment1, true /* isBoosted */, clientTransaction);
+
+ verify(unembeddedActivity).assignLayer(t, 0);
+ verify(fragment1).assignLayer(t, 1);
+ verify(fragment2).assignLayer(t, 2);
+ verify(t).setLayer(task.mDecorSurfaceContainer.mContainerSurface, 3);
+
+ verify(t).setVisibility(task.mDecorSurfaceContainer.mContainerSurface, true);
+ verify(t).merge(clientTransaction);
+
+ clearInvocations(t);
+ clearInvocations(unembeddedActivity);
+ clearInvocations(fragment1);
+ clearInvocations(fragment2);
+
+ // The decor surface should be placed just above the owner TaskFragment and the cover
+ // surface should hide.
+ task.moveOrCreateDecorSurfaceFor(fragment1);
+ task.setDecorSurfaceBoosted(fragment1, false /* isBoosted */, clientTransaction);
+
+ verify(unembeddedActivity).assignLayer(t, 0);
+ verify(fragment1).assignLayer(t, 1);
+ verify(t).setLayer(task.mDecorSurfaceContainer.mContainerSurface, 2);
+ verify(fragment2).assignLayer(t, 3);
+
+ verify(t).setVisibility(task.mDecorSurfaceContainer.mContainerSurface, true);
+ verify(t).merge(clientTransaction);
+
+ }
+
+ @Test
public void testMoveTaskFragmentsToBottomIfNeeded() {
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index ce890f6..ff7129c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -70,6 +70,8 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static java.lang.Integer.MAX_VALUE;
+
import android.app.ActivityManager;
import android.content.res.Configuration;
import android.graphics.Color;
@@ -1120,8 +1122,7 @@
mDisplayContent.getDisplayRotation().setRotation(mDisplayContent.getRotation() + 1);
mDisplayContent.setLastHasContent();
- mDisplayContent.requestChangeTransitionIfNeeded(1 /* any changes */,
- null /* displayChange */);
+ mDisplayContent.requestChangeTransition(1 /* any changes */, null /* displayChange */);
assertEquals(WindowContainer.SYNC_STATE_NONE, statusBar.mSyncState);
assertEquals(WindowContainer.SYNC_STATE_NONE, navBar.mSyncState);
assertEquals(WindowContainer.SYNC_STATE_NONE, screenDecor.mSyncState);
@@ -1191,7 +1192,7 @@
mDisplayContent.getDisplayRotation().setRotation(mDisplayContent.getRotation() + 1);
final int anyChanges = 1;
mDisplayContent.setLastHasContent();
- mDisplayContent.requestChangeTransitionIfNeeded(anyChanges, null /* displayChange */);
+ mDisplayContent.collectDisplayChange(transition);
transition.setKnownConfigChanges(mDisplayContent, anyChanges);
final AsyncRotationController asyncRotationController =
mDisplayContent.getAsyncRotationController();
@@ -1254,7 +1255,7 @@
// so the previous async rotation controller should still exist.
mDisplayContent.getDisplayRotation().setRotation(mDisplayContent.getRotation() + 1);
mDisplayContent.setLastHasContent();
- mDisplayContent.requestChangeTransitionIfNeeded(1 /* changes */, null /* displayChange */);
+ mDisplayContent.requestChangeTransition(1 /* changes */, null /* displayChange */);
assertTrue(mDisplayContent.hasTopFixedRotationLaunchingApp());
assertNotNull(mDisplayContent.getAsyncRotationController());
@@ -1300,7 +1301,7 @@
mDisplayContent.setFixedRotationLaunchingAppUnchecked(app);
registerTestTransitionPlayer();
mDisplayContent.setLastHasContent();
- mDisplayContent.requestChangeTransitionIfNeeded(1 /* changes */, null /* displayChange */);
+ mDisplayContent.requestChangeTransition(1 /* changes */, null /* displayChange */);
assertNotNull(mDisplayContent.getAsyncRotationController());
mDisplayContent.setFixedRotationLaunchingAppUnchecked(null);
assertNull("Clear rotation controller if rotation is not changed",
@@ -2571,6 +2572,37 @@
}
@Test
+ public void testConfigAtEndReparent() {
+ final TransitionController controller = mDisplayContent.mTransitionController;
+ Transition transit = createTestTransition(TRANSIT_CHANGE, controller);
+ final TestTransitionPlayer player = registerTestTransitionPlayer();
+
+ final Task taskOrig = createTask(mDisplayContent);
+ taskOrig.getConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 200, 300));
+ final Task task = createTask(mDisplayContent);
+ task.getConfiguration().windowConfiguration.setBounds(new Rect(10, 10, 200, 300));
+ final ActivityRecord activity = createActivityRecord(taskOrig);
+ activity.setVisibleRequested(true);
+ activity.setVisible(true);
+
+ controller.moveToCollecting(transit);
+ transit.collect(taskOrig);
+ transit.collect(task);
+ transit.collect(activity);
+ transit.setConfigAtEnd(taskOrig);
+ activity.reparent(task, MAX_VALUE);
+ task.moveToFront("test");
+
+ controller.requestStartTransition(transit, task, null, null);
+ player.start();
+ // config-at-end flag must propagate up to task even when reparented (since config-at-end
+ // only cares about after-end state).
+ assertTrue(player.mLastReady.getChange(
+ task.mRemoteToken.toWindowContainerToken()).hasFlags(FLAG_CONFIG_AT_END));
+ player.finish();
+ }
+
+ @Test
public void testReadyTrackerBasics() {
final TransitionController controller = new TestTransitionController(
mock(ActivityTaskManagerService.class));
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 80fb44a..72bedf2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -39,6 +39,8 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -305,12 +307,12 @@
final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
wallpaperController.adjustWallpaperWindows();
// Wallpaper is visible because the show-when-locked activity is translucent.
- assertTrue(wallpaperController.isWallpaperTarget(wallpaperWindow));
+ assertSame(wallpaperWindow, wallpaperController.getWallpaperTarget());
behind.mActivityRecord.setShowWhenLocked(true);
wallpaperController.adjustWallpaperWindows();
// Wallpaper is invisible because the lowest show-when-locked activity is opaque.
- assertTrue(wallpaperController.isWallpaperTarget(null));
+ assertNull(wallpaperController.getWallpaperTarget());
// A show-when-locked wallpaper is used for lockscreen. So the top wallpaper should
// be the one that is not show-when-locked.
@@ -374,10 +376,10 @@
// The activity in restore-below task should not be the target if keyguard is not locked.
mDisplayContent.mWallpaperController.adjustWallpaperWindows();
assertNotEquals(appWin, mDisplayContent.mWallpaperController.getWallpaperTarget());
- // The activity in restore-below task should be the target if keyguard is occluded.
+ // The activity in restore-below task should not be the target if keyguard is occluded.
doReturn(true).when(mDisplayContent).isKeyguardLocked();
mDisplayContent.mWallpaperController.adjustWallpaperWindows();
- assertEquals(appWin, mDisplayContent.mWallpaperController.getWallpaperTarget());
+ assertNotEquals(appWin, mDisplayContent.mWallpaperController.getWallpaperTarget());
}
@Test
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index a58cf5f..a35a35a 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -152,6 +152,10 @@
public static final boolean ENABLE_TIME_CHANGE_CORRECTION
= SystemProperties.getBoolean("persist.debug.time_correction", true);
+ private static final boolean USE_DEDICATED_HANDLER_THREAD =
+ SystemProperties.getBoolean("persist.debug.use_dedicated_handler_thread",
+ Flags.useDedicatedHandlerThread());
+
static final boolean DEBUG = false; // Never submit with true
static final boolean DEBUG_RESPONSE_STATS = DEBUG || Log.isLoggable(TAG, Log.DEBUG);
static final boolean COMPRESS_TIME = false;
@@ -404,11 +408,11 @@
IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_STARTED);
getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, filter,
- null, /* scheduler= */ Flags.useDedicatedHandlerThread() ? mHandler : null);
+ null, /* scheduler= */ USE_DEDICATED_HANDLER_THREAD ? mHandler : null);
getContext().registerReceiverAsUser(new UidRemovedReceiver(), UserHandle.ALL,
new IntentFilter(ACTION_UID_REMOVED), null,
- /* scheduler= */ Flags.useDedicatedHandlerThread() ? mHandler : null);
+ /* scheduler= */ USE_DEDICATED_HANDLER_THREAD ? mHandler : null);
mRealTimeSnapshot = SystemClock.elapsedRealtime();
mSystemTimeSnapshot = System.currentTimeMillis();
@@ -497,7 +501,7 @@
}
private Handler getUsageEventProcessingHandler() {
- if (Flags.useDedicatedHandlerThread()) {
+ if (USE_DEDICATED_HANDLER_THREAD) {
return new H(UsageStatsHandlerThread.get().getLooper());
} else {
return new H(BackgroundThread.get().getLooper());
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 5a52968..ae4faa8 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.voiceinteraction;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
@@ -1072,8 +1073,10 @@
// If visEnabledKey is set to true (or absent), we try following VIS path.
String csPkgName = mContext.getResources()
.getString(R.string.config_defaultContextualSearchPackageName);
- if (!csPkgName.equals(getCurInteractor(
- Binder.getCallingUserHandle().getIdentifier()).getPackageName())) {
+ ComponentName currInteractor =
+ getCurInteractor(Binder.getCallingUserHandle().getIdentifier());
+ if (currInteractor == null
+ || !csPkgName.equals(currInteractor.getPackageName())) {
// Check if the interactor can handle Contextual Search.
// If not, return failure.
Slog.w(TAG, "Contextual Search not supported yet. Returning failure.");
@@ -2718,7 +2721,7 @@
}
launchIntent.setComponent(resolveInfo.getComponentInfo().getComponentName());
launchIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NO_ANIMATION
- | FLAG_ACTIVITY_NO_USER_ACTION);
+ | FLAG_ACTIVITY_NO_USER_ACTION | FLAG_ACTIVITY_CLEAR_TASK);
launchIntent.putExtras(args);
boolean isAssistDataAllowed = mAtmInternal.isAssistDataAllowed();
final List<ActivityAssistInfo> records = mAtmInternal.getTopVisibleActivities();
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 048b1b2..ff4be55 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -2797,7 +2797,9 @@
/**
* Determines whether there are any ongoing {@link PhoneAccount#CAPABILITY_SELF_MANAGED}
- * calls for a given {@code packageName} and {@code userHandle}.
+ * calls for a given {@code packageName} and {@code userHandle}. If UserHandle.ALL or a user
+ * that isn't the calling user is passed in, the caller will need to have granted the ability
+ * to interact across users.
*
* @param packageName the package name of the app to check calls for.
* @param userHandle the user handle to check calls for.
@@ -2816,41 +2818,7 @@
if (service != null) {
try {
return service.isInSelfManagedCall(packageName, userHandle,
- mContext.getOpPackageName(), false);
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException isInSelfManagedCall: " + e);
- e.rethrowFromSystemServer();
- return false;
- }
- } else {
- throw new IllegalStateException("Telecom service is not present");
- }
- }
-
- /**
- * Determines whether there are any ongoing {@link PhoneAccount#CAPABILITY_SELF_MANAGED}
- * calls for a given {@code packageName} amongst all users, given that detectForAllUsers is true
- * and the caller has the ability to interact across users. If detectForAllUsers isn't enabled,
- * the calls will be checked against the caller.
- *
- * @param packageName the package name of the app to check calls for.
- * @param detectForAllUsers indicates if calls should be detected across all users.
- * @return {@code true} if there are ongoing calls, {@code false} otherwise.
- * @throws SecurityException if detectForAllUsers is true and the caller does not grant the
- * ability to interact across users.
- * @hide
- */
- @SystemApi
- @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
- @RequiresPermission(allOf = {Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
- Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
- public boolean isInSelfManagedCall(@NonNull String packageName,
- boolean detectForAllUsers) {
- ITelecomService service = getTelecomService();
- if (service != null) {
- try {
- return service.isInSelfManagedCall(packageName, null,
- mContext.getOpPackageName(), detectForAllUsers);
+ mContext.getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "RemoteException isInSelfManagedCall: " + e);
e.rethrowFromSystemServer();
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 302a472..112471b 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -401,7 +401,7 @@
* @see TelecomServiceImpl#isInSelfManagedCall
*/
boolean isInSelfManagedCall(String packageName, in UserHandle userHandle,
- String callingPackage, boolean detectForAllUsers);
+ String callingPackage);
/**
* @see TelecomServiceImpl#addCall
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 2150b5d..df32fbd 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3315,6 +3315,18 @@
"support_no_reply_timer_for_cfnry_bool";
/**
+ * No reply time value to be sent to network for call forwarding on no reply
+ * (CFNRy 3GPP TS 24.082 version 17.0 section 3).
+ * Controls time in seconds for the no reply condition on in the call forwarding
+ * settings UI.
+ * This is available when {@link #KEY_SUPPORT_NO_REPLY_TIMER_FOR_CFNRY_BOOL} is true.
+ *
+ * @hide
+ */
+ public static final String KEY_NO_REPLY_TIMER_FOR_CFNRY_SEC_INT =
+ "no_reply_timer_for_cfnry_sec_int";
+
+ /**
* List of the FAC (feature access codes) to dial as a normal call.
* @hide
*/
@@ -10258,6 +10270,31 @@
@FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
public static final String KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY =
"cellular_service_capabilities_int_array";
+ /**
+ * Transition delay from BT to Cellular on Wear.
+ * Specifies delay when transitioning away from BT.
+ * This minimizes the duration of the netTransitionWakelock held by ConnectivityService
+ * whenever the primary/default network disappears, while still allowing some amount of time
+ * for BT to reconnect before we enable cell.
+ *
+ * If set as -1 then value from resources will be used
+ *
+ * @hide
+ */
+ public static final String KEY_WEAR_CONNECTIVITY_BT_TO_CELL_DELAY_MS_INT =
+ "proxy_connectivity_delay_cell";
+
+ /**
+ * Transition delay from BT to Cellular on Wear.
+ * If wifi connected it extends delay that has been started for BT to Cellular transition
+ * to avoid Wifi thrashing turning Cell radio and causing higher battery drain.
+ *
+ * If set as -1 then value from resources will be used
+ *
+ * @hide
+ */
+ public static final String KEY_WEAR_CONNECTIVITY_EXTEND_BT_TO_CELL_DELAY_ON_WIFI_MS_INT =
+ "wifi_connectivity_extend_cell_delay";
/** The default value for every variable. */
private static final PersistableBundle sDefaults;
@@ -10641,6 +10678,7 @@
sDefaults.putBoolean(KEY_VT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_RTT_CALL_BOOL, true);
sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORT_NO_REPLY_TIMER_FOR_CFNRY_BOOL, true);
+ sDefaults.putInt(KEY_NO_REPLY_TIMER_FOR_CFNRY_SEC_INT, 20);
sDefaults.putStringArray(KEY_FEATURE_ACCESS_CODES_STRING_ARRAY, null);
sDefaults.putBoolean(KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL, false);
sDefaults.putBoolean(KEY_SHOW_PRECISE_FAILED_CAUSE_BOOL, false);
@@ -11054,6 +11092,8 @@
sDefaults.putStringArray(KEY_CARRIER_SERVICE_NAME_STRING_ARRAY, new String[0]);
sDefaults.putStringArray(KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY, new String[0]);
sDefaults.putIntArray(KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY, new int[]{1, 2, 3});
+ sDefaults.putInt(KEY_WEAR_CONNECTIVITY_BT_TO_CELL_DELAY_MS_INT, -1);
+ sDefaults.putInt(KEY_WEAR_CONNECTIVITY_EXTEND_BT_TO_CELL_DELAY_ON_WIFI_MS_INT, -1);
}
/**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 88acbab..a047b97 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -18852,7 +18852,7 @@
*/
@SystemApi
@FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
- @RequiresPermission(android.Manifest.permission.DUMP)
+ @RequiresPermission(android.Manifest.permission.READ_DROPBOX_DATA)
public void persistEmergencyCallDiagnosticData(@NonNull String dropboxTag,
@NonNull EmergencyCallDiagnosticData data) {
try {
diff --git a/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl b/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl
index e69b60b..c349599 100644
--- a/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl
+++ b/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl
@@ -26,5 +26,5 @@
{
void onQualifiedNetworkTypesChanged(int apnTypes, in int[] qualifiedNetworkTypes);
void onNetworkValidationRequested(int networkCapability, IIntegerConsumer callback);
- void onReconnectQualifedNetworkType(int apnTypes, int qualifiedNetworkType);
+ void onReconnectQualifiedNetworkType(int apnTypes, int qualifiedNetworkType);
}
diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java
index 7bfe04d..f775de6 100644
--- a/telephony/java/android/telephony/data/QualifiedNetworksService.java
+++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java
@@ -238,7 +238,7 @@
@AccessNetworkConstants.RadioAccessNetworkType int qualifiedNetworkType) {
if (mCallback != null) {
try {
- mCallback.onReconnectQualifedNetworkType(apnTypes, qualifiedNetworkType);
+ mCallback.onReconnectQualifiedNetworkType(apnTypes, qualifiedNetworkType);
} catch (RemoteException e) {
loge("Failed to call onReconnectQualifiedNetworkType. " + e);
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index 17f91eb..060015b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -40,9 +40,10 @@
constructor(
protected val flicker: LegacyFlickerTest,
protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
+ protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
) {
- protected val tapl: LauncherInstrumentation by lazy {
- LauncherInstrumentation().also { it.expectedRotationCheckEnabled = true }
+ init {
+ tapl.setExpectedRotationCheckEnabled(true)
}
private val logTag = this::class.java.simpleName
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt
deleted file mode 100644
index 8faf224..0000000
--- a/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.input.screenshot
-
-import androidx.test.platform.app.InstrumentationRegistry
-import platform.test.screenshot.GoldenImagePathManager
-import platform.test.screenshot.PathConfig
-
-/** A [GoldenImagePathManager] that should be used for all Input screenshot tests. */
-class InputGoldenImagePathManager(
- pathConfig: PathConfig,
- assetsPathRelativeToBuildRoot: String
-) :
- GoldenImagePathManager(
- appContext = InstrumentationRegistry.getInstrumentation().context,
- assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
- deviceLocalPath =
- InstrumentationRegistry.getInstrumentation()
- .targetContext
- .filesDir
- .absolutePath
- .toString() + "/input_screenshots",
- pathConfig = pathConfig,
- ) {
- override fun toString(): String {
- // This string is appended to all actual/expected screenshots on the device, so make sure
- // it is a static value.
- return "InputGoldenImagePathManager"
- }
-}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenPathManager.kt
similarity index 62%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
copy to tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenPathManager.kt
index f5fba7f..9f14b136 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenPathManager.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -14,31 +14,28 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.screenshot.util
+package com.android.input.screenshot
import androidx.test.platform.app.InstrumentationRegistry
-import platform.test.screenshot.GoldenImagePathManager
+import platform.test.screenshot.GoldenPathManager
import platform.test.screenshot.PathConfig
-/** A [GoldenImagePathManager] that should be used for all Settings screenshot tests. */
-class SettingsGoldenImagePathManager(
- pathConfig: PathConfig,
- assetsPathRelativeToBuildRoot: String
-) :
- GoldenImagePathManager(
+/** A [GoldenPathManager] that should be used for all Input screenshot tests. */
+class InputGoldenPathManager(pathConfig: PathConfig, assetsPathRelativeToBuildRoot: String) :
+ GoldenPathManager(
appContext = InstrumentationRegistry.getInstrumentation().context,
assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
deviceLocalPath =
- InstrumentationRegistry.getInstrumentation()
- .targetContext
- .filesDir
- .absolutePath
- .toString() + "/settings_screenshots",
+ InstrumentationRegistry.getInstrumentation()
+ .targetContext
+ .filesDir
+ .absolutePath
+ .toString() + "/input_screenshots",
pathConfig = pathConfig,
) {
override fun toString(): String {
// This string is appended to all actual/expected screenshots on the device, so make sure
// it is a static value.
- return "SettingsGoldenImagePathManager"
+ return "InputGoldenPathManager"
}
}
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
index 75dab41..2f40896 100644
--- a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
@@ -44,7 +44,7 @@
private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
private val screenshotRule =
ScreenshotTestRule(
- InputGoldenImagePathManager(
+ InputGoldenPathManager(
getEmulatedDevicePathConfig(emulationSpec),
assetsPathRelativeToBuildRoot
)
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 755636a..75284c7 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -43,13 +43,13 @@
import android.os.test.TestLooper;
import android.provider.DeviceConfig;
import android.util.AtomicFile;
-import android.util.LongArrayQueue;
import android.util.Xml;
+import android.utils.LongArrayQueue;
+import android.utils.XmlUtils;
import androidx.test.InstrumentationRegistry;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.PackageWatchdog.HealthCheckState;
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 78f277e..f70a17d 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
@@ -1051,6 +1051,9 @@
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 name Ljava/lang/String;
+ MethodParameters:
+ Name Flags
+ <no name> mandated
private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex(java.lang.String, java.lang.String);
descriptor: (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
@@ -1074,6 +1077,12 @@
0 18 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex;
0 18 3 longName Ljava/lang/String;
0 18 4 shortName Ljava/lang/String;
+ MethodParameters:
+ Name Flags
+ <no name> synthetic
+ <no name> synthetic
+ <no name>
+ <no name>
Signature: #x // (Ljava/lang/String;Ljava/lang/String;)V
RuntimeInvisibleAnnotations:
x: #x()
@@ -1224,6 +1233,9 @@
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 name Ljava/lang/String;
+ MethodParameters:
+ Name Flags
+ <no name> mandated
private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple();
descriptor: (Ljava/lang/String;I)V
@@ -1239,6 +1251,10 @@
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
+ MethodParameters:
+ Name Flags
+ <no name> synthetic
+ <no name> synthetic
Signature: #x // ()V
private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $values();
@@ -2031,6 +2047,9 @@
Start Length Slot Name Signature
0 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1;
0 10 1 this$0 Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+ MethodParameters:
+ Name Flags
+ <no name> final mandated
public java.lang.Integer get();
descriptor: ()Ljava/lang/Integer;
@@ -2147,6 +2166,9 @@
Start Length Slot Name Signature
0 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3;
0 10 1 this$0 Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+ MethodParameters:
+ Name Flags
+ <no name> final mandated
public java.lang.Integer get();
descriptor: ()Ljava/lang/Integer;
@@ -2304,6 +2326,9 @@
Start Length Slot Name Signature
0 15 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass;
0 15 1 this$0 Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+ MethodParameters:
+ Name Flags
+ <no name> final mandated
}
SourceFile: "TinyFrameworkNestedClasses.java"
RuntimeInvisibleAnnotations:
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 406cb74..37de857 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
@@ -795,6 +795,9 @@
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ MethodParameters:
+ Name Flags
+ <no name> mandated
private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex(java.lang.String, java.lang.String);
descriptor: (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
@@ -815,6 +818,12 @@
RuntimeInvisibleAnnotations:
x: #x()
android.hosttest.annotation.HostSideTestStub
+ MethodParameters:
+ Name Flags
+ <no name> synthetic
+ <no name> synthetic
+ <no name>
+ <no name>
public java.lang.String getLongName();
descriptor: ()Ljava/lang/String;
@@ -969,6 +978,9 @@
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ MethodParameters:
+ Name Flags
+ <no name> mandated
private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple();
descriptor: (Ljava/lang/String;I)V
@@ -986,6 +998,10 @@
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ MethodParameters:
+ Name Flags
+ <no name> synthetic
+ <no name> synthetic
private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $values();
descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
@@ -1769,6 +1785,9 @@
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ MethodParameters:
+ Name Flags
+ <no name> final mandated
}
InnerClasses:
public #x= #x of #x; // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
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 c673262..c9c607c 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
@@ -1225,6 +1225,9 @@
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ MethodParameters:
+ Name Flags
+ <no name> mandated
private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex(java.lang.String, java.lang.String);
descriptor: (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
@@ -1257,6 +1260,12 @@
RuntimeInvisibleAnnotations:
x: #x()
android.hosttest.annotation.HostSideTestStub
+ MethodParameters:
+ Name Flags
+ <no name> synthetic
+ <no name> synthetic
+ <no name>
+ <no name>
public java.lang.String getLongName();
descriptor: ()Ljava/lang/String;
@@ -1453,6 +1462,9 @@
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ MethodParameters:
+ Name Flags
+ <no name> mandated
private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple();
descriptor: (Ljava/lang/String;I)V
@@ -1474,6 +1486,10 @@
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ MethodParameters:
+ Name Flags
+ <no name> synthetic
+ <no name> synthetic
private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $values();
descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
@@ -2578,6 +2594,9 @@
RuntimeVisibleAnnotations:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ MethodParameters:
+ Name Flags
+ <no name> final mandated
public java.lang.Integer get();
descriptor: ()Ljava/lang/Integer;
@@ -2745,6 +2764,9 @@
RuntimeVisibleAnnotations:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ MethodParameters:
+ Name Flags
+ <no name> final mandated
public java.lang.Integer get();
descriptor: ()Ljava/lang/Integer;
@@ -2977,6 +2999,9 @@
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ MethodParameters:
+ Name Flags
+ <no name> final mandated
}
InnerClasses:
public #x= #x of #x; // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
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 406cb74..37de857 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
@@ -795,6 +795,9 @@
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ MethodParameters:
+ Name Flags
+ <no name> mandated
private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex(java.lang.String, java.lang.String);
descriptor: (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
@@ -815,6 +818,12 @@
RuntimeInvisibleAnnotations:
x: #x()
android.hosttest.annotation.HostSideTestStub
+ MethodParameters:
+ Name Flags
+ <no name> synthetic
+ <no name> synthetic
+ <no name>
+ <no name>
public java.lang.String getLongName();
descriptor: ()Ljava/lang/String;
@@ -969,6 +978,9 @@
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ MethodParameters:
+ Name Flags
+ <no name> mandated
private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple();
descriptor: (Ljava/lang/String;I)V
@@ -986,6 +998,10 @@
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ MethodParameters:
+ Name Flags
+ <no name> synthetic
+ <no name> synthetic
private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $values();
descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
@@ -1769,6 +1785,9 @@
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ MethodParameters:
+ Name Flags
+ <no name> final mandated
}
InnerClasses:
public #x= #x of #x; // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
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 4fd5701..a57907d 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
@@ -1532,6 +1532,9 @@
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ MethodParameters:
+ Name Flags
+ <no name> mandated
private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumComplex(java.lang.String, java.lang.String);
descriptor: (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
@@ -1569,6 +1572,12 @@
RuntimeInvisibleAnnotations:
x: #x()
android.hosttest.annotation.HostSideTestStub
+ MethodParameters:
+ Name Flags
+ <no name> synthetic
+ <no name> synthetic
+ <no name>
+ <no name>
public java.lang.String getLongName();
descriptor: ()Ljava/lang/String;
@@ -1798,6 +1807,9 @@
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ MethodParameters:
+ Name Flags
+ <no name> mandated
private com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple();
descriptor: (Ljava/lang/String;I)V
@@ -1824,6 +1836,10 @@
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ MethodParameters:
+ Name Flags
+ <no name> synthetic
+ <no name> synthetic
private static com.android.hoststubgen.test.tinyframework.TinyFrameworkEnumSimple[] $values();
descriptor: ()[Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple;
@@ -3190,6 +3206,9 @@
RuntimeVisibleAnnotations:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ MethodParameters:
+ Name Flags
+ <no name> final mandated
public java.lang.Integer get();
descriptor: ()Ljava/lang/Integer;
@@ -3407,6 +3426,9 @@
RuntimeVisibleAnnotations:
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ MethodParameters:
+ Name Flags
+ <no name> final mandated
public java.lang.Integer get();
descriptor: ()Ljava/lang/Integer;
@@ -3704,6 +3726,9 @@
com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
x: #x()
com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+ MethodParameters:
+ Name Flags
+ <no name> final mandated
}
InnerClasses:
public #x= #x of #x; // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses